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


##########
frontend/src/app/workspace/service/preset/preset.service.spec.ts:
##########
@@ -16,432 +16,435 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-// TODO: rewrite skipped tests away from Jasmine done/fail callbacks (#4861).
-// These stubs make the it.skip bodies type-check without running.
-declare function done(): void;
-declare function fail(message?: string): never;
-
-// TODO(vitest): done callbacks need rewrite to async/Promise pattern; these 
specs are skipped pending follow-up — tracked in #4861.
-
-// import { HttpClientTestingModule, HttpTestingController } from 
"@angular/common/http/testing";
-// import { TestBed, inject, fakeAsync, tick, flush, discardPeriodicTasks } 
from "@angular/core/testing";
-// import { BrowserAnimationsModule } from 
"@angular/platform-browser/animations";
-// import { NzMessageModule, NzMessageService } from "ng-zorro-antd/message";
-// import { AppSettings } from "src/app/common/app-setting";
-// import { DictionaryService } from 
"src/app/common/service/user/user-dictionary/dictionary.service";
-// import { JointUIService } from "../joint-ui/joint-ui.service";
-// import { OperatorMetadataService } from 
"../operator-metadata/operator-metadata.service";
-// import { StubOperatorMetadataService } from 
"../operator-metadata/stub-operator-metadata.service";
-// import { UndoRedoService } from "../undo-redo/undo-redo.service";
-// import { WorkflowActionService } from 
"../workflow-graph/model/workflow-action.service";
-// import { WorkflowUtilService } from 
"../workflow-graph/util/workflow-util.service";
-// import { PresetService } from "./preset.service";
-// import { mockPresetEnabledPredicate, mockPoint } from 
"../workflow-graph/model/mock-workflow-data";
-// import { CustomJSONSchema7 } from 
"../../types/custom-json-schema.interface";
-// import { mockPresetEnabledSchema } from 
"../operator-metadata/mock-operator-metadata.data";
-
-// describe("PresetService", () => {
-//   let presetService: PresetService;
-//   let httpMock: HttpTestingController;
-
-//   beforeEach(fakeAsync(() => {
-//     TestBed.configureTestingModule({
-//       providers: [
-//         PresetService,
-//         DictionaryService,
-//         WorkflowActionService,
-//         WorkflowUtilService,
-//         JointUIService,
-//         UndoRedoService,
-//         { provide: OperatorMetadataService, useClass: 
StubOperatorMetadataService },
-//       ],
-//       imports: [NzMessageModule, HttpClientTestingModule, 
BrowserAnimationsModule],
-//     });
-
-//     presetService = TestBed.inject(PresetService);
-//     httpMock = TestBed.inject(HttpTestingController);
-
-//     // handle dict initialization
-//     const testDict = { a: "a", b: "b", c: "c" };
-//     const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//     
httpMock.expectOne(`${AppSettings.getApiEndpoint()}/users/auth/status`).flush({ 
name: "testUser", uid: 1 }); // allow autologin by userService
-//     httpMock.expectOne(`${dictApiEndpoint}/get`).flush({ code: 1, result: 
testDict });
-//     httpMock.verify();
-//     tick();
-//   }));
-
-//   it("should be created", inject([WorkflowActionService], (injectedService: 
WorkflowActionService) => {
-//     expect(injectedService).toBeTruthy();
-//   }));
-
-//   describe("preset I/O", () => {
-//     it.skip("should emit an event when presets are applied", () => {
-//       presetService.applyPresetStream.subscribe(value => {
-//         expect(value).toEqual({ type: "testType", target: "testTarget", 
preset: { testPresetKey: "testPresetValue" } });
-//         done();
-//       });
-//       presetService.applyPreset("testType", "testTarget", { testPresetKey: 
"testPresetValue" });
-//     });
-
-//     it.skip("should emit an event when presets are saved", () => {
-//       presetService.savePresetsStream.subscribe(value => {
-//         expect(value).toEqual({
-//           type: "testType",
-//           target: "testTarget",
-//           presets: [{ testPresetKey: "testPresetValue" }],
-//         });
-//         done();
-//       });
-//       presetService.savePresets("testType", "testTarget", [{ testPresetKey: 
"testPresetValue" }]);
-//     });
-
-//     it("should save to user dictionary when presets are saved", 
fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-
-//       presetService.savePresets("testType", "testTarget", [{ testPresetKey: 
"testPresetValue" }]);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       expect(
-//         userDictionaryService.getUserDictionary()[`${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`]
-//       ).toEqual(JSON.stringify([{ testPresetKey: "testPresetValue" }]));
-//     }));
-
-//     it("should save amended entry to user dictionary when a preset is 
deleted", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//       const presetDictKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const initialPresets = [{ testPresetKey: "testPresetValue" }, { 
testPresetKey: "testPresetValue2" }];
-//       const endPresets = initialPresets.slice(0, 1);
-
-//       presetService.savePresets("testType", "testTarget", initialPresets);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(initialPresets));
-
-//       presetService.deletePreset("testType", "testTarget", 
initialPresets[1]);
-//       savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(endPresets));
-//     }));
-
-//     it("should save amended entry to user dictionary when a preset is 
updated", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//       const presetDictKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const initialPresets = [{ testPresetKey: "testPresetValue" }, { 
testPresetKey: "testPresetValue2" }];
-//       const updatedPreset = { testPresetKey: "testPresetValue3" };
-//       const endPresets = [initialPresets[0], updatedPreset];
-
-//       presetService.savePresets("testType", "testTarget", initialPresets);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(initialPresets));
-
-//       presetService.updatePreset("testType", "testTarget", 
initialPresets[1], updatedPreset);
-//       savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(endPresets));
-//     }));
-
-//     it("should delete from dictionary service when empty preset list is 
saved", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-
-//       presetService.savePresets("testType", "testTarget", []);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/delete`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("DELETE");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       expect(
-//         userDictionaryService.getUserDictionary()[`${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`]
-//       ).toBeUndefined();
-//       flush();
-//     }));
-
-//     it("should get user presets from the user dictionary", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-
-//       // cant use an expression as a property name, so the dict must be 
setup via assignment :(
-//       const testPresetKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const testPresets = [{ testPresetKey: "testPresetValue" }];
-//       const testDict: any = {};
-//       testDict[testPresetKey] = JSON.stringify(testPresets);
-
-//       vi.spyOn(userDictionaryService, 
"forceGetUserDictionary").mockReturnValue(testDict);
-
-//       presetService = new PresetService(
-//         userDictionaryService,
-//         TestBed.inject(NzMessageService),
-//         TestBed.inject(WorkflowActionService),
-//         TestBed.inject(OperatorMetadataService)
-//       );
-
-//       const presets = presetService.getPresets("testType", "testTarget");
-//       expect(presets).toEqual(testPresets);
-//     }));
-//   });
-
-//   describe("operator preset handling", () => {
-//     let workflowActionService: WorkflowActionService;
-
-//     beforeEach(() => {
-//       workflowActionService = TestBed.inject(WorkflowActionService);
-//       workflowActionService.addOperator(mockPresetEnabledPredicate, 
mockPoint);
-//       
workflowActionService.setOperatorProperty(mockPresetEnabledPredicate.operatorID,
 {
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     });
-
-//     it("should not set operator properties if a non operator preset is 
applied", fakeAsync(() => {
-//       presetService.applyPreset("NotAnOperator", "NotAnOperatorID", { 
NotAPresetProperty: "presetApplied" });
-//       tick();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-
-//     it("should not set operator properties if an invalid operator preset is 
applied", fakeAsync(() => {
-//       expect(() => {
-//         presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, {
-//           NotAPresetProperty: "presetApplied",
-//         });
-//         flush();
-//       }).toThrow();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-
-//     it("should set operator properties if a valid operator preset is 
applied", fakeAsync(() => {
-//       presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, { presetProperty: "presetApplied" });
-//       tick();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "presetApplied",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-//   });
-
-//   describe("operator preset validation", () => {
-//     beforeEach(() => {
-//       const workflowActionService = TestBed.inject(WorkflowActionService);
-//       workflowActionService.addOperator(mockPresetEnabledPredicate, 
mockPoint);
-//     });
-
-//     it("should reject an empty preset", () => {
-//       expect(presetService.isValidOperatorPreset({}, 
mockPresetEnabledPredicate.operatorID)).toBe(false);
-//     });
-
-//     it("should reject preset with the wrong properties", () => {
-//       expect(
-//         presetService.isValidOperatorPreset(
-//           { wrongProperty: "wrongpropertyPreset" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(false);
-//     });
-
-//     it("should reject preset with empty properties", () => {
-//       expect(
-//         presetService.isValidOperatorPreset({ presetProperty: "" }, 
mockPresetEnabledPredicate.operatorID)
-//       ).toBe(false);
-//     });
-
-//     it("should accept a properly formatted preset", () => {
-//       expect(
-//         presetService.isValidOperatorPreset(
-//           { presetProperty: "presetHasBeenApplied" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(true);
-//     });
-
-//     it("should reject new presets if they already exist", () => {
-//       vi.spyOn(presetService, "getPresets").mockReturnValue([{ 
presetProperty: "presetHasBeenApplied" }]);
-
-//       expect(
-//         presetService.isValidNewOperatorPreset(
-//           { presetProperty: "presetHasBeenApplied" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(false);
-//     });
-
-//     it("should accept new presets if they are novel", () => {
-//       vi.spyOn(presetService, "getPresets").mockReturnValue([{ 
presetProperty: "presetHasBeenApplied" }]);
-
-//       expect(
-//         presetService.isValidNewOperatorPreset(
-//           { presetProperty: "alternatePreset" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(true);
-//     });
-//   });
-
-//   describe("operator preset schema generation", () => {
-//     it("should generate a preset schema", () => {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           presetProperty: {
-//             type: "string",
-//             description: "property that can be saved in presets",
-//             title: "presetProperty",
-//             "enable-presets": true,
-//           },
-//           normalProperty: {
-//             type: "string",
-//             description: "property that is excluded in presets",
-//             title: "normalProperty",
-//           },
-//         },
-//         required: ["normalProperty"],
-//       };
-
-//       const presetSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           presetProperty: {
-//             type: "string",
-//             description: "property that can be saved in presets",
-//             title: "presetProperty",
-//             "enable-presets": true,
-//           },
-//         },
-//         required: ["presetProperty"],
-//         additionalProperties: false,
-//       };
-
-//       
expect(PresetService.getOperatorPresetSchema(operatorSchema)).toEqual(presetSchema);
-//     });
-
-//     it("should throw an error if the operator schema has no properties", () 
=> {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {},
-//         required: ["normalProperty"],
-//       };
-
-//       expect(() => 
PresetService.getOperatorPresetSchema(operatorSchema)).toThrow();
-//     });
-
-//     it("should throw an error if the operator schema has no preset 
properties", () => {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           normalProperty: {
-//             type: "string",
-//             description: "property that is excluded in presets",
-//             title: "normalProperty",
-//           },
-//         },
-//         required: ["normalProperty"],
-//       };
-
-//       expect(() => 
PresetService.getOperatorPresetSchema(operatorSchema)).toThrow();
-//     });
-//   });
-
-//   describe("operator preset generation", () => {
-//     describe("getOperatorPreset - throw errors if invalid", () => {
-//       it("should throw an error if operator properties is empty", () => {
-//         expect(() => 
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, 
{})).toThrow();
-//       });
-
-//       it("should throw an error if operator properties doesn't have all the 
preset properties", () => {
-//         expect(() =>
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, { 
wrongProperty: "wrongpropertyPreset" })
-//         ).toThrow();
-//       });
-
-//       it("should return a preset if operator properties has all the preset 
properties", () => {
-//         expect(
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, { 
presetProperty: "presetPropertyValue" })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-
-//       it("should return a preset if operator properties has a superset of 
preset properties", () => {
-//         expect(
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, {
-//             presetProperty: "presetPropertyValue",
-//             otherProperty: "othervalue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-//     });
-
-//     describe("filterOperatorPresetProperties - doesn't guarantee preset is 
valid", () => {
-//       it("should never add to operator properties", () => {
-//         
expect(PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {})).toEqual({});
-//       });
-
-//       it("should filter out non preset properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             wrongProperty: "wrongpropertyPreset",
-//           })
-//         ).toEqual({});
-//       });
-
-//       it("should not filter out preset properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             presetProperty: "presetPropertyValue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-
-//       it("should filter out non preset properties and leave behind preset 
properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             presetProperty: "presetPropertyValue",
-//             otherProperty: "othervalue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-//     });
-//   });
-// });
+
+import { TestBed } from "@angular/core/testing";
+import { NzMessageService } from "ng-zorro-antd/message";
+import { of } from "rxjs";
+import { UserConfigService } from 
"src/app/common/service/user/config/user-config.service";
+import { CustomJSONSchema7 } from "../../types/custom-json-schema.interface";
+import { JointUIService } from "../joint-ui/joint-ui.service";
+import { mockPresetEnabledSchema } from 
"../operator-metadata/mock-operator-metadata.data";
+import { OperatorMetadataService } from 
"../operator-metadata/operator-metadata.service";
+import { StubOperatorMetadataService } from 
"../operator-metadata/stub-operator-metadata.service";
+import { UndoRedoService } from "../undo-redo/undo-redo.service";
+import { mockPoint, mockPresetEnabledPredicate } from 
"../workflow-graph/model/mock-workflow-data";
+import { WorkflowActionService } from 
"../workflow-graph/model/workflow-action.service";
+import { WorkflowUtilService } from 
"../workflow-graph/util/workflow-util.service";
+import { commonTestProviders } from "../../../common/testing/test-utils";
+import { Preset, PresetService } from "./preset.service";
+
+// Ajv 8 defaults to strict mode and rejects unknown keywords at compile time, 
so
+// `isValidOperatorPreset` (which compiles operator schemas containing the
+// 'enable-presets' marker) throws before it can validate. Register the keyword
+// once as a no-op so the validation paths are exercisable in tests.
+const ajvInstance = (PresetService as any).ajv;
+if (!ajvInstance.RULES.keywords["enable-presets"]) {
+  ajvInstance.addKeyword({ keyword: "enable-presets", schemaType: "boolean" });
+}
+
+describe("PresetService", () => {
+  let userConfigStub: {
+    fetchKey: ReturnType<typeof vi.fn>;
+    set: ReturnType<typeof vi.fn>;
+    delete: ReturnType<typeof vi.fn>;
+  };
+  let messageStub: {
+    success: ReturnType<typeof vi.fn>;
+    error: ReturnType<typeof vi.fn>;
+    info: ReturnType<typeof vi.fn>;
+    warning: ReturnType<typeof vi.fn>;
+  };
+  let presetService: PresetService;
+  let workflowActionService: WorkflowActionService;
+
+  const presetType = "operator";
+  const presetTarget = mockPresetEnabledPredicate.operatorType;
+  const presetDictKey = `${presetType}-${presetTarget}`;
+
+  beforeEach(() => {
+    userConfigStub = {
+      fetchKey: vi.fn().mockReturnValue(of(null)),
+      set: vi.fn().mockReturnValue(of(void 0)),
+      delete: vi.fn().mockReturnValue(of(void 0)),
+    };
+    messageStub = {
+      success: vi.fn(),
+      error: vi.fn(),
+      info: vi.fn(),
+      warning: vi.fn(),
+    };
+
+    TestBed.configureTestingModule({
+      providers: [
+        PresetService,
+        WorkflowActionService,
+        WorkflowUtilService,
+        JointUIService,
+        UndoRedoService,
+        { provide: OperatorMetadataService, useClass: 
StubOperatorMetadataService },
+        { provide: UserConfigService, useValue: userConfigStub },
+        { provide: NzMessageService, useValue: messageStub },
+        ...commonTestProviders,
+      ],
+    });
+
+    presetService = TestBed.inject(PresetService);
+    workflowActionService = TestBed.inject(WorkflowActionService);
+  });
+
+  it("should be created", () => {
+    expect(presetService).toBeTruthy();
+  });
+
+  describe("preset I/O", () => {
+    it("emits an event on applyPresetStream when a preset is applied", () => {
+      const seen: any[] = [];
+      const sub = presetService.applyPresetStream.subscribe(value => 
seen.push(value));
+
+      const preset: Preset = { presetProperty: "applied" };
+      presetService.applyPreset("nonOperatorType", "anyTarget", preset);
+
+      expect(seen).toEqual([{ type: "nonOperatorType", target: "anyTarget", 
preset }]);
+      sub.unsubscribe();
+    });
+
+    it("emits an event on savePresetsStream when presets are saved", () => {
+      const seen: any[] = [];
+      const sub = presetService.savePresetsStream.subscribe(value => 
seen.push(value));
+
+      const presets: Preset[] = [{ presetProperty: "v1" }];
+      presetService.savePresets(presetType, presetTarget, presets);
+
+      expect(seen).toEqual([{ type: presetType, target: presetTarget, presets 
}]);
+      sub.unsubscribe();
+    });
+
+    it("writes through UserConfigService.set when saving a non-empty preset 
list", () => {
+      const presets: Preset[] = [{ presetProperty: "v1" }];
+      presetService.savePresets(presetType, presetTarget, presets);
+
+      expect(userConfigStub.set).toHaveBeenCalledTimes(1);
+      expect(userConfigStub.set).toHaveBeenCalledWith(presetDictKey, 
JSON.stringify(presets));
+      expect(userConfigStub.delete).not.toHaveBeenCalled();
+    });
+
+    it("calls UserConfigService.delete instead of set when saving an empty 
preset list", () => {
+      presetService.savePresets(presetType, presetTarget, []);
+
+      expect(userConfigStub.delete).toHaveBeenCalledTimes(1);
+      expect(userConfigStub.delete).toHaveBeenCalledWith(presetDictKey);
+      expect(userConfigStub.set).not.toHaveBeenCalled();
+    });
+
+    it("displays the success toast by default when saving presets", () => {
+      presetService.savePresets(presetType, presetTarget, [{ presetProperty: 
"v1" }]);
+      expect(messageStub.success).toHaveBeenCalledWith("Preset saved");
+    });
+
+    it("suppresses the toast when displayMessage is explicitly null", () => {
+      presetService.savePresets(presetType, presetTarget, [{ presetProperty: 
"v1" }], null);
+      expect(messageStub.success).not.toHaveBeenCalled();
+      expect(messageStub.error).not.toHaveBeenCalled();
+    });
+
+    it("createPreset appends to existing presets and writes back", () => {
+      const existing: Preset[] = [{ presetProperty: "v1" }];
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify(existing)));
+
+      presetService.createPreset(presetType, presetTarget, { presetProperty: 
"v2" });
+
+      expect(userConfigStub.set).toHaveBeenCalledWith(
+        presetDictKey,
+        JSON.stringify([{ presetProperty: "v1" }, { presetProperty: "v2" }])
+      );
+    });
+
+    it("createPreset does not write the preset back when it already exists", 
() => {
+      // The service throws inside an RxJS subscribe handler, so the throw is 
reported
+      // asynchronously and is not catchable with toThrow. Verify the no-op 
behaviorally.
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify([{ 
presetProperty: "v1" }])));
+
+      try {
+        presetService.createPreset(presetType, presetTarget, { presetProperty: 
"v1" });
+      } catch {
+        // swallow: the throw may surface synchronously depending on RxJS 
behavior, but
+        // we only care that no save happened.
+      }
+
+      expect(userConfigStub.set).not.toHaveBeenCalled();
+      expect(userConfigStub.delete).not.toHaveBeenCalled();
+    });

Review Comment:
   This test triggers `PresetService.createPreset`'s error path (duplicate 
preset), which throws *inside an RxJS `subscribe` handler* in `PresetService`. 
RxJS reports errors thrown from `next` handlers via its unhandled-error 
mechanism (often asynchronously), so the surrounding try/catch is unlikely to 
intercept it and the suite can fail due to an uncaught exception. Capture RxJS 
unhandled errors (e.g., via `rxjs` `config.onUnhandledError`) and assert on the 
captured error, or change the test to avoid invoking a path that throws from 
within `subscribe`.



##########
frontend/src/app/workspace/service/preset/preset.service.spec.ts:
##########
@@ -16,432 +16,435 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-// TODO: rewrite skipped tests away from Jasmine done/fail callbacks (#4861).
-// These stubs make the it.skip bodies type-check without running.
-declare function done(): void;
-declare function fail(message?: string): never;
-
-// TODO(vitest): done callbacks need rewrite to async/Promise pattern; these 
specs are skipped pending follow-up — tracked in #4861.
-
-// import { HttpClientTestingModule, HttpTestingController } from 
"@angular/common/http/testing";
-// import { TestBed, inject, fakeAsync, tick, flush, discardPeriodicTasks } 
from "@angular/core/testing";
-// import { BrowserAnimationsModule } from 
"@angular/platform-browser/animations";
-// import { NzMessageModule, NzMessageService } from "ng-zorro-antd/message";
-// import { AppSettings } from "src/app/common/app-setting";
-// import { DictionaryService } from 
"src/app/common/service/user/user-dictionary/dictionary.service";
-// import { JointUIService } from "../joint-ui/joint-ui.service";
-// import { OperatorMetadataService } from 
"../operator-metadata/operator-metadata.service";
-// import { StubOperatorMetadataService } from 
"../operator-metadata/stub-operator-metadata.service";
-// import { UndoRedoService } from "../undo-redo/undo-redo.service";
-// import { WorkflowActionService } from 
"../workflow-graph/model/workflow-action.service";
-// import { WorkflowUtilService } from 
"../workflow-graph/util/workflow-util.service";
-// import { PresetService } from "./preset.service";
-// import { mockPresetEnabledPredicate, mockPoint } from 
"../workflow-graph/model/mock-workflow-data";
-// import { CustomJSONSchema7 } from 
"../../types/custom-json-schema.interface";
-// import { mockPresetEnabledSchema } from 
"../operator-metadata/mock-operator-metadata.data";
-
-// describe("PresetService", () => {
-//   let presetService: PresetService;
-//   let httpMock: HttpTestingController;
-
-//   beforeEach(fakeAsync(() => {
-//     TestBed.configureTestingModule({
-//       providers: [
-//         PresetService,
-//         DictionaryService,
-//         WorkflowActionService,
-//         WorkflowUtilService,
-//         JointUIService,
-//         UndoRedoService,
-//         { provide: OperatorMetadataService, useClass: 
StubOperatorMetadataService },
-//       ],
-//       imports: [NzMessageModule, HttpClientTestingModule, 
BrowserAnimationsModule],
-//     });
-
-//     presetService = TestBed.inject(PresetService);
-//     httpMock = TestBed.inject(HttpTestingController);
-
-//     // handle dict initialization
-//     const testDict = { a: "a", b: "b", c: "c" };
-//     const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//     
httpMock.expectOne(`${AppSettings.getApiEndpoint()}/users/auth/status`).flush({ 
name: "testUser", uid: 1 }); // allow autologin by userService
-//     httpMock.expectOne(`${dictApiEndpoint}/get`).flush({ code: 1, result: 
testDict });
-//     httpMock.verify();
-//     tick();
-//   }));
-
-//   it("should be created", inject([WorkflowActionService], (injectedService: 
WorkflowActionService) => {
-//     expect(injectedService).toBeTruthy();
-//   }));
-
-//   describe("preset I/O", () => {
-//     it.skip("should emit an event when presets are applied", () => {
-//       presetService.applyPresetStream.subscribe(value => {
-//         expect(value).toEqual({ type: "testType", target: "testTarget", 
preset: { testPresetKey: "testPresetValue" } });
-//         done();
-//       });
-//       presetService.applyPreset("testType", "testTarget", { testPresetKey: 
"testPresetValue" });
-//     });
-
-//     it.skip("should emit an event when presets are saved", () => {
-//       presetService.savePresetsStream.subscribe(value => {
-//         expect(value).toEqual({
-//           type: "testType",
-//           target: "testTarget",
-//           presets: [{ testPresetKey: "testPresetValue" }],
-//         });
-//         done();
-//       });
-//       presetService.savePresets("testType", "testTarget", [{ testPresetKey: 
"testPresetValue" }]);
-//     });
-
-//     it("should save to user dictionary when presets are saved", 
fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-
-//       presetService.savePresets("testType", "testTarget", [{ testPresetKey: 
"testPresetValue" }]);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       expect(
-//         userDictionaryService.getUserDictionary()[`${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`]
-//       ).toEqual(JSON.stringify([{ testPresetKey: "testPresetValue" }]));
-//     }));
-
-//     it("should save amended entry to user dictionary when a preset is 
deleted", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//       const presetDictKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const initialPresets = [{ testPresetKey: "testPresetValue" }, { 
testPresetKey: "testPresetValue2" }];
-//       const endPresets = initialPresets.slice(0, 1);
-
-//       presetService.savePresets("testType", "testTarget", initialPresets);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(initialPresets));
-
-//       presetService.deletePreset("testType", "testTarget", 
initialPresets[1]);
-//       savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(endPresets));
-//     }));
-
-//     it("should save amended entry to user dictionary when a preset is 
updated", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//       const presetDictKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const initialPresets = [{ testPresetKey: "testPresetValue" }, { 
testPresetKey: "testPresetValue2" }];
-//       const updatedPreset = { testPresetKey: "testPresetValue3" };
-//       const endPresets = [initialPresets[0], updatedPreset];
-
-//       presetService.savePresets("testType", "testTarget", initialPresets);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(initialPresets));
-
-//       presetService.updatePreset("testType", "testTarget", 
initialPresets[1], updatedPreset);
-//       savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(endPresets));
-//     }));
-
-//     it("should delete from dictionary service when empty preset list is 
saved", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-
-//       presetService.savePresets("testType", "testTarget", []);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/delete`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("DELETE");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       expect(
-//         userDictionaryService.getUserDictionary()[`${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`]
-//       ).toBeUndefined();
-//       flush();
-//     }));
-
-//     it("should get user presets from the user dictionary", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-
-//       // cant use an expression as a property name, so the dict must be 
setup via assignment :(
-//       const testPresetKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const testPresets = [{ testPresetKey: "testPresetValue" }];
-//       const testDict: any = {};
-//       testDict[testPresetKey] = JSON.stringify(testPresets);
-
-//       vi.spyOn(userDictionaryService, 
"forceGetUserDictionary").mockReturnValue(testDict);
-
-//       presetService = new PresetService(
-//         userDictionaryService,
-//         TestBed.inject(NzMessageService),
-//         TestBed.inject(WorkflowActionService),
-//         TestBed.inject(OperatorMetadataService)
-//       );
-
-//       const presets = presetService.getPresets("testType", "testTarget");
-//       expect(presets).toEqual(testPresets);
-//     }));
-//   });
-
-//   describe("operator preset handling", () => {
-//     let workflowActionService: WorkflowActionService;
-
-//     beforeEach(() => {
-//       workflowActionService = TestBed.inject(WorkflowActionService);
-//       workflowActionService.addOperator(mockPresetEnabledPredicate, 
mockPoint);
-//       
workflowActionService.setOperatorProperty(mockPresetEnabledPredicate.operatorID,
 {
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     });
-
-//     it("should not set operator properties if a non operator preset is 
applied", fakeAsync(() => {
-//       presetService.applyPreset("NotAnOperator", "NotAnOperatorID", { 
NotAPresetProperty: "presetApplied" });
-//       tick();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-
-//     it("should not set operator properties if an invalid operator preset is 
applied", fakeAsync(() => {
-//       expect(() => {
-//         presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, {
-//           NotAPresetProperty: "presetApplied",
-//         });
-//         flush();
-//       }).toThrow();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-
-//     it("should set operator properties if a valid operator preset is 
applied", fakeAsync(() => {
-//       presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, { presetProperty: "presetApplied" });
-//       tick();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "presetApplied",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-//   });
-
-//   describe("operator preset validation", () => {
-//     beforeEach(() => {
-//       const workflowActionService = TestBed.inject(WorkflowActionService);
-//       workflowActionService.addOperator(mockPresetEnabledPredicate, 
mockPoint);
-//     });
-
-//     it("should reject an empty preset", () => {
-//       expect(presetService.isValidOperatorPreset({}, 
mockPresetEnabledPredicate.operatorID)).toBe(false);
-//     });
-
-//     it("should reject preset with the wrong properties", () => {
-//       expect(
-//         presetService.isValidOperatorPreset(
-//           { wrongProperty: "wrongpropertyPreset" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(false);
-//     });
-
-//     it("should reject preset with empty properties", () => {
-//       expect(
-//         presetService.isValidOperatorPreset({ presetProperty: "" }, 
mockPresetEnabledPredicate.operatorID)
-//       ).toBe(false);
-//     });
-
-//     it("should accept a properly formatted preset", () => {
-//       expect(
-//         presetService.isValidOperatorPreset(
-//           { presetProperty: "presetHasBeenApplied" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(true);
-//     });
-
-//     it("should reject new presets if they already exist", () => {
-//       vi.spyOn(presetService, "getPresets").mockReturnValue([{ 
presetProperty: "presetHasBeenApplied" }]);
-
-//       expect(
-//         presetService.isValidNewOperatorPreset(
-//           { presetProperty: "presetHasBeenApplied" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(false);
-//     });
-
-//     it("should accept new presets if they are novel", () => {
-//       vi.spyOn(presetService, "getPresets").mockReturnValue([{ 
presetProperty: "presetHasBeenApplied" }]);
-
-//       expect(
-//         presetService.isValidNewOperatorPreset(
-//           { presetProperty: "alternatePreset" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(true);
-//     });
-//   });
-
-//   describe("operator preset schema generation", () => {
-//     it("should generate a preset schema", () => {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           presetProperty: {
-//             type: "string",
-//             description: "property that can be saved in presets",
-//             title: "presetProperty",
-//             "enable-presets": true,
-//           },
-//           normalProperty: {
-//             type: "string",
-//             description: "property that is excluded in presets",
-//             title: "normalProperty",
-//           },
-//         },
-//         required: ["normalProperty"],
-//       };
-
-//       const presetSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           presetProperty: {
-//             type: "string",
-//             description: "property that can be saved in presets",
-//             title: "presetProperty",
-//             "enable-presets": true,
-//           },
-//         },
-//         required: ["presetProperty"],
-//         additionalProperties: false,
-//       };
-
-//       
expect(PresetService.getOperatorPresetSchema(operatorSchema)).toEqual(presetSchema);
-//     });
-
-//     it("should throw an error if the operator schema has no properties", () 
=> {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {},
-//         required: ["normalProperty"],
-//       };
-
-//       expect(() => 
PresetService.getOperatorPresetSchema(operatorSchema)).toThrow();
-//     });
-
-//     it("should throw an error if the operator schema has no preset 
properties", () => {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           normalProperty: {
-//             type: "string",
-//             description: "property that is excluded in presets",
-//             title: "normalProperty",
-//           },
-//         },
-//         required: ["normalProperty"],
-//       };
-
-//       expect(() => 
PresetService.getOperatorPresetSchema(operatorSchema)).toThrow();
-//     });
-//   });
-
-//   describe("operator preset generation", () => {
-//     describe("getOperatorPreset - throw errors if invalid", () => {
-//       it("should throw an error if operator properties is empty", () => {
-//         expect(() => 
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, 
{})).toThrow();
-//       });
-
-//       it("should throw an error if operator properties doesn't have all the 
preset properties", () => {
-//         expect(() =>
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, { 
wrongProperty: "wrongpropertyPreset" })
-//         ).toThrow();
-//       });
-
-//       it("should return a preset if operator properties has all the preset 
properties", () => {
-//         expect(
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, { 
presetProperty: "presetPropertyValue" })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-
-//       it("should return a preset if operator properties has a superset of 
preset properties", () => {
-//         expect(
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, {
-//             presetProperty: "presetPropertyValue",
-//             otherProperty: "othervalue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-//     });
-
-//     describe("filterOperatorPresetProperties - doesn't guarantee preset is 
valid", () => {
-//       it("should never add to operator properties", () => {
-//         
expect(PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {})).toEqual({});
-//       });
-
-//       it("should filter out non preset properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             wrongProperty: "wrongpropertyPreset",
-//           })
-//         ).toEqual({});
-//       });
-
-//       it("should not filter out preset properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             presetProperty: "presetPropertyValue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-
-//       it("should filter out non preset properties and leave behind preset 
properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             presetProperty: "presetPropertyValue",
-//             otherProperty: "othervalue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-//     });
-//   });
-// });
+
+import { TestBed } from "@angular/core/testing";
+import { NzMessageService } from "ng-zorro-antd/message";
+import { of } from "rxjs";
+import { UserConfigService } from 
"src/app/common/service/user/config/user-config.service";
+import { CustomJSONSchema7 } from "../../types/custom-json-schema.interface";
+import { JointUIService } from "../joint-ui/joint-ui.service";
+import { mockPresetEnabledSchema } from 
"../operator-metadata/mock-operator-metadata.data";
+import { OperatorMetadataService } from 
"../operator-metadata/operator-metadata.service";
+import { StubOperatorMetadataService } from 
"../operator-metadata/stub-operator-metadata.service";
+import { UndoRedoService } from "../undo-redo/undo-redo.service";
+import { mockPoint, mockPresetEnabledPredicate } from 
"../workflow-graph/model/mock-workflow-data";
+import { WorkflowActionService } from 
"../workflow-graph/model/workflow-action.service";
+import { WorkflowUtilService } from 
"../workflow-graph/util/workflow-util.service";
+import { commonTestProviders } from "../../../common/testing/test-utils";
+import { Preset, PresetService } from "./preset.service";
+
+// Ajv 8 defaults to strict mode and rejects unknown keywords at compile time, 
so
+// `isValidOperatorPreset` (which compiles operator schemas containing the
+// 'enable-presets' marker) throws before it can validate. Register the keyword
+// once as a no-op so the validation paths are exercisable in tests.
+const ajvInstance = (PresetService as any).ajv;
+if (!ajvInstance.RULES.keywords["enable-presets"]) {
+  ajvInstance.addKeyword({ keyword: "enable-presets", schemaType: "boolean" });

Review Comment:
   The Ajv keyword guard relies on Ajv's internal `RULES.keywords` structure. 
This is not part of Ajv's public API and can break across Ajv versions; if 
`RULES` is missing/renamed, the spec will crash before tests run. Prefer using 
a public check (e.g., Ajv's keyword lookup API) or wrap the check/add in a safe 
try/catch so the suite doesn't depend on Ajv internals.
   



##########
frontend/src/app/workspace/service/preset/preset.service.spec.ts:
##########
@@ -16,432 +16,435 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-// TODO: rewrite skipped tests away from Jasmine done/fail callbacks (#4861).
-// These stubs make the it.skip bodies type-check without running.
-declare function done(): void;
-declare function fail(message?: string): never;
-
-// TODO(vitest): done callbacks need rewrite to async/Promise pattern; these 
specs are skipped pending follow-up — tracked in #4861.
-
-// import { HttpClientTestingModule, HttpTestingController } from 
"@angular/common/http/testing";
-// import { TestBed, inject, fakeAsync, tick, flush, discardPeriodicTasks } 
from "@angular/core/testing";
-// import { BrowserAnimationsModule } from 
"@angular/platform-browser/animations";
-// import { NzMessageModule, NzMessageService } from "ng-zorro-antd/message";
-// import { AppSettings } from "src/app/common/app-setting";
-// import { DictionaryService } from 
"src/app/common/service/user/user-dictionary/dictionary.service";
-// import { JointUIService } from "../joint-ui/joint-ui.service";
-// import { OperatorMetadataService } from 
"../operator-metadata/operator-metadata.service";
-// import { StubOperatorMetadataService } from 
"../operator-metadata/stub-operator-metadata.service";
-// import { UndoRedoService } from "../undo-redo/undo-redo.service";
-// import { WorkflowActionService } from 
"../workflow-graph/model/workflow-action.service";
-// import { WorkflowUtilService } from 
"../workflow-graph/util/workflow-util.service";
-// import { PresetService } from "./preset.service";
-// import { mockPresetEnabledPredicate, mockPoint } from 
"../workflow-graph/model/mock-workflow-data";
-// import { CustomJSONSchema7 } from 
"../../types/custom-json-schema.interface";
-// import { mockPresetEnabledSchema } from 
"../operator-metadata/mock-operator-metadata.data";
-
-// describe("PresetService", () => {
-//   let presetService: PresetService;
-//   let httpMock: HttpTestingController;
-
-//   beforeEach(fakeAsync(() => {
-//     TestBed.configureTestingModule({
-//       providers: [
-//         PresetService,
-//         DictionaryService,
-//         WorkflowActionService,
-//         WorkflowUtilService,
-//         JointUIService,
-//         UndoRedoService,
-//         { provide: OperatorMetadataService, useClass: 
StubOperatorMetadataService },
-//       ],
-//       imports: [NzMessageModule, HttpClientTestingModule, 
BrowserAnimationsModule],
-//     });
-
-//     presetService = TestBed.inject(PresetService);
-//     httpMock = TestBed.inject(HttpTestingController);
-
-//     // handle dict initialization
-//     const testDict = { a: "a", b: "b", c: "c" };
-//     const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//     
httpMock.expectOne(`${AppSettings.getApiEndpoint()}/users/auth/status`).flush({ 
name: "testUser", uid: 1 }); // allow autologin by userService
-//     httpMock.expectOne(`${dictApiEndpoint}/get`).flush({ code: 1, result: 
testDict });
-//     httpMock.verify();
-//     tick();
-//   }));
-
-//   it("should be created", inject([WorkflowActionService], (injectedService: 
WorkflowActionService) => {
-//     expect(injectedService).toBeTruthy();
-//   }));
-
-//   describe("preset I/O", () => {
-//     it.skip("should emit an event when presets are applied", () => {
-//       presetService.applyPresetStream.subscribe(value => {
-//         expect(value).toEqual({ type: "testType", target: "testTarget", 
preset: { testPresetKey: "testPresetValue" } });
-//         done();
-//       });
-//       presetService.applyPreset("testType", "testTarget", { testPresetKey: 
"testPresetValue" });
-//     });
-
-//     it.skip("should emit an event when presets are saved", () => {
-//       presetService.savePresetsStream.subscribe(value => {
-//         expect(value).toEqual({
-//           type: "testType",
-//           target: "testTarget",
-//           presets: [{ testPresetKey: "testPresetValue" }],
-//         });
-//         done();
-//       });
-//       presetService.savePresets("testType", "testTarget", [{ testPresetKey: 
"testPresetValue" }]);
-//     });
-
-//     it("should save to user dictionary when presets are saved", 
fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-
-//       presetService.savePresets("testType", "testTarget", [{ testPresetKey: 
"testPresetValue" }]);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       expect(
-//         userDictionaryService.getUserDictionary()[`${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`]
-//       ).toEqual(JSON.stringify([{ testPresetKey: "testPresetValue" }]));
-//     }));
-
-//     it("should save amended entry to user dictionary when a preset is 
deleted", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//       const presetDictKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const initialPresets = [{ testPresetKey: "testPresetValue" }, { 
testPresetKey: "testPresetValue2" }];
-//       const endPresets = initialPresets.slice(0, 1);
-
-//       presetService.savePresets("testType", "testTarget", initialPresets);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(initialPresets));
-
-//       presetService.deletePreset("testType", "testTarget", 
initialPresets[1]);
-//       savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(endPresets));
-//     }));
-
-//     it("should save amended entry to user dictionary when a preset is 
updated", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//       const presetDictKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const initialPresets = [{ testPresetKey: "testPresetValue" }, { 
testPresetKey: "testPresetValue2" }];
-//       const updatedPreset = { testPresetKey: "testPresetValue3" };
-//       const endPresets = [initialPresets[0], updatedPreset];
-
-//       presetService.savePresets("testType", "testTarget", initialPresets);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(initialPresets));
-
-//       presetService.updatePreset("testType", "testTarget", 
initialPresets[1], updatedPreset);
-//       savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(endPresets));
-//     }));
-
-//     it("should delete from dictionary service when empty preset list is 
saved", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-
-//       presetService.savePresets("testType", "testTarget", []);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/delete`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("DELETE");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       expect(
-//         userDictionaryService.getUserDictionary()[`${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`]
-//       ).toBeUndefined();
-//       flush();
-//     }));
-
-//     it("should get user presets from the user dictionary", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-
-//       // cant use an expression as a property name, so the dict must be 
setup via assignment :(
-//       const testPresetKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const testPresets = [{ testPresetKey: "testPresetValue" }];
-//       const testDict: any = {};
-//       testDict[testPresetKey] = JSON.stringify(testPresets);
-
-//       vi.spyOn(userDictionaryService, 
"forceGetUserDictionary").mockReturnValue(testDict);
-
-//       presetService = new PresetService(
-//         userDictionaryService,
-//         TestBed.inject(NzMessageService),
-//         TestBed.inject(WorkflowActionService),
-//         TestBed.inject(OperatorMetadataService)
-//       );
-
-//       const presets = presetService.getPresets("testType", "testTarget");
-//       expect(presets).toEqual(testPresets);
-//     }));
-//   });
-
-//   describe("operator preset handling", () => {
-//     let workflowActionService: WorkflowActionService;
-
-//     beforeEach(() => {
-//       workflowActionService = TestBed.inject(WorkflowActionService);
-//       workflowActionService.addOperator(mockPresetEnabledPredicate, 
mockPoint);
-//       
workflowActionService.setOperatorProperty(mockPresetEnabledPredicate.operatorID,
 {
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     });
-
-//     it("should not set operator properties if a non operator preset is 
applied", fakeAsync(() => {
-//       presetService.applyPreset("NotAnOperator", "NotAnOperatorID", { 
NotAPresetProperty: "presetApplied" });
-//       tick();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-
-//     it("should not set operator properties if an invalid operator preset is 
applied", fakeAsync(() => {
-//       expect(() => {
-//         presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, {
-//           NotAPresetProperty: "presetApplied",
-//         });
-//         flush();
-//       }).toThrow();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-
-//     it("should set operator properties if a valid operator preset is 
applied", fakeAsync(() => {
-//       presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, { presetProperty: "presetApplied" });
-//       tick();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "presetApplied",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-//   });
-
-//   describe("operator preset validation", () => {
-//     beforeEach(() => {
-//       const workflowActionService = TestBed.inject(WorkflowActionService);
-//       workflowActionService.addOperator(mockPresetEnabledPredicate, 
mockPoint);
-//     });
-
-//     it("should reject an empty preset", () => {
-//       expect(presetService.isValidOperatorPreset({}, 
mockPresetEnabledPredicate.operatorID)).toBe(false);
-//     });
-
-//     it("should reject preset with the wrong properties", () => {
-//       expect(
-//         presetService.isValidOperatorPreset(
-//           { wrongProperty: "wrongpropertyPreset" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(false);
-//     });
-
-//     it("should reject preset with empty properties", () => {
-//       expect(
-//         presetService.isValidOperatorPreset({ presetProperty: "" }, 
mockPresetEnabledPredicate.operatorID)
-//       ).toBe(false);
-//     });
-
-//     it("should accept a properly formatted preset", () => {
-//       expect(
-//         presetService.isValidOperatorPreset(
-//           { presetProperty: "presetHasBeenApplied" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(true);
-//     });
-
-//     it("should reject new presets if they already exist", () => {
-//       vi.spyOn(presetService, "getPresets").mockReturnValue([{ 
presetProperty: "presetHasBeenApplied" }]);
-
-//       expect(
-//         presetService.isValidNewOperatorPreset(
-//           { presetProperty: "presetHasBeenApplied" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(false);
-//     });
-
-//     it("should accept new presets if they are novel", () => {
-//       vi.spyOn(presetService, "getPresets").mockReturnValue([{ 
presetProperty: "presetHasBeenApplied" }]);
-
-//       expect(
-//         presetService.isValidNewOperatorPreset(
-//           { presetProperty: "alternatePreset" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(true);
-//     });
-//   });
-
-//   describe("operator preset schema generation", () => {
-//     it("should generate a preset schema", () => {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           presetProperty: {
-//             type: "string",
-//             description: "property that can be saved in presets",
-//             title: "presetProperty",
-//             "enable-presets": true,
-//           },
-//           normalProperty: {
-//             type: "string",
-//             description: "property that is excluded in presets",
-//             title: "normalProperty",
-//           },
-//         },
-//         required: ["normalProperty"],
-//       };
-
-//       const presetSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           presetProperty: {
-//             type: "string",
-//             description: "property that can be saved in presets",
-//             title: "presetProperty",
-//             "enable-presets": true,
-//           },
-//         },
-//         required: ["presetProperty"],
-//         additionalProperties: false,
-//       };
-
-//       
expect(PresetService.getOperatorPresetSchema(operatorSchema)).toEqual(presetSchema);
-//     });
-
-//     it("should throw an error if the operator schema has no properties", () 
=> {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {},
-//         required: ["normalProperty"],
-//       };
-
-//       expect(() => 
PresetService.getOperatorPresetSchema(operatorSchema)).toThrow();
-//     });
-
-//     it("should throw an error if the operator schema has no preset 
properties", () => {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           normalProperty: {
-//             type: "string",
-//             description: "property that is excluded in presets",
-//             title: "normalProperty",
-//           },
-//         },
-//         required: ["normalProperty"],
-//       };
-
-//       expect(() => 
PresetService.getOperatorPresetSchema(operatorSchema)).toThrow();
-//     });
-//   });
-
-//   describe("operator preset generation", () => {
-//     describe("getOperatorPreset - throw errors if invalid", () => {
-//       it("should throw an error if operator properties is empty", () => {
-//         expect(() => 
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, 
{})).toThrow();
-//       });
-
-//       it("should throw an error if operator properties doesn't have all the 
preset properties", () => {
-//         expect(() =>
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, { 
wrongProperty: "wrongpropertyPreset" })
-//         ).toThrow();
-//       });
-
-//       it("should return a preset if operator properties has all the preset 
properties", () => {
-//         expect(
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, { 
presetProperty: "presetPropertyValue" })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-
-//       it("should return a preset if operator properties has a superset of 
preset properties", () => {
-//         expect(
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, {
-//             presetProperty: "presetPropertyValue",
-//             otherProperty: "othervalue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-//     });
-
-//     describe("filterOperatorPresetProperties - doesn't guarantee preset is 
valid", () => {
-//       it("should never add to operator properties", () => {
-//         
expect(PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {})).toEqual({});
-//       });
-
-//       it("should filter out non preset properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             wrongProperty: "wrongpropertyPreset",
-//           })
-//         ).toEqual({});
-//       });
-
-//       it("should not filter out preset properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             presetProperty: "presetPropertyValue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-
-//       it("should filter out non preset properties and leave behind preset 
properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             presetProperty: "presetPropertyValue",
-//             otherProperty: "othervalue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-//     });
-//   });
-// });
+
+import { TestBed } from "@angular/core/testing";
+import { NzMessageService } from "ng-zorro-antd/message";
+import { of } from "rxjs";
+import { UserConfigService } from 
"src/app/common/service/user/config/user-config.service";
+import { CustomJSONSchema7 } from "../../types/custom-json-schema.interface";
+import { JointUIService } from "../joint-ui/joint-ui.service";
+import { mockPresetEnabledSchema } from 
"../operator-metadata/mock-operator-metadata.data";
+import { OperatorMetadataService } from 
"../operator-metadata/operator-metadata.service";
+import { StubOperatorMetadataService } from 
"../operator-metadata/stub-operator-metadata.service";
+import { UndoRedoService } from "../undo-redo/undo-redo.service";
+import { mockPoint, mockPresetEnabledPredicate } from 
"../workflow-graph/model/mock-workflow-data";
+import { WorkflowActionService } from 
"../workflow-graph/model/workflow-action.service";
+import { WorkflowUtilService } from 
"../workflow-graph/util/workflow-util.service";
+import { commonTestProviders } from "../../../common/testing/test-utils";
+import { Preset, PresetService } from "./preset.service";
+
+// Ajv 8 defaults to strict mode and rejects unknown keywords at compile time, 
so
+// `isValidOperatorPreset` (which compiles operator schemas containing the
+// 'enable-presets' marker) throws before it can validate. Register the keyword
+// once as a no-op so the validation paths are exercisable in tests.
+const ajvInstance = (PresetService as any).ajv;
+if (!ajvInstance.RULES.keywords["enable-presets"]) {
+  ajvInstance.addKeyword({ keyword: "enable-presets", schemaType: "boolean" });
+}
+
+describe("PresetService", () => {
+  let userConfigStub: {
+    fetchKey: ReturnType<typeof vi.fn>;
+    set: ReturnType<typeof vi.fn>;
+    delete: ReturnType<typeof vi.fn>;
+  };
+  let messageStub: {
+    success: ReturnType<typeof vi.fn>;
+    error: ReturnType<typeof vi.fn>;
+    info: ReturnType<typeof vi.fn>;
+    warning: ReturnType<typeof vi.fn>;
+  };
+  let presetService: PresetService;
+  let workflowActionService: WorkflowActionService;
+
+  const presetType = "operator";
+  const presetTarget = mockPresetEnabledPredicate.operatorType;
+  const presetDictKey = `${presetType}-${presetTarget}`;
+
+  beforeEach(() => {
+    userConfigStub = {
+      fetchKey: vi.fn().mockReturnValue(of(null)),
+      set: vi.fn().mockReturnValue(of(void 0)),
+      delete: vi.fn().mockReturnValue(of(void 0)),
+    };
+    messageStub = {
+      success: vi.fn(),
+      error: vi.fn(),
+      info: vi.fn(),
+      warning: vi.fn(),
+    };
+
+    TestBed.configureTestingModule({
+      providers: [
+        PresetService,
+        WorkflowActionService,
+        WorkflowUtilService,
+        JointUIService,
+        UndoRedoService,
+        { provide: OperatorMetadataService, useClass: 
StubOperatorMetadataService },
+        { provide: UserConfigService, useValue: userConfigStub },
+        { provide: NzMessageService, useValue: messageStub },
+        ...commonTestProviders,
+      ],
+    });
+
+    presetService = TestBed.inject(PresetService);
+    workflowActionService = TestBed.inject(WorkflowActionService);
+  });
+
+  it("should be created", () => {
+    expect(presetService).toBeTruthy();
+  });
+
+  describe("preset I/O", () => {
+    it("emits an event on applyPresetStream when a preset is applied", () => {
+      const seen: any[] = [];
+      const sub = presetService.applyPresetStream.subscribe(value => 
seen.push(value));
+
+      const preset: Preset = { presetProperty: "applied" };
+      presetService.applyPreset("nonOperatorType", "anyTarget", preset);
+
+      expect(seen).toEqual([{ type: "nonOperatorType", target: "anyTarget", 
preset }]);
+      sub.unsubscribe();
+    });
+
+    it("emits an event on savePresetsStream when presets are saved", () => {
+      const seen: any[] = [];
+      const sub = presetService.savePresetsStream.subscribe(value => 
seen.push(value));
+
+      const presets: Preset[] = [{ presetProperty: "v1" }];
+      presetService.savePresets(presetType, presetTarget, presets);
+
+      expect(seen).toEqual([{ type: presetType, target: presetTarget, presets 
}]);
+      sub.unsubscribe();
+    });
+
+    it("writes through UserConfigService.set when saving a non-empty preset 
list", () => {
+      const presets: Preset[] = [{ presetProperty: "v1" }];
+      presetService.savePresets(presetType, presetTarget, presets);
+
+      expect(userConfigStub.set).toHaveBeenCalledTimes(1);
+      expect(userConfigStub.set).toHaveBeenCalledWith(presetDictKey, 
JSON.stringify(presets));
+      expect(userConfigStub.delete).not.toHaveBeenCalled();
+    });
+
+    it("calls UserConfigService.delete instead of set when saving an empty 
preset list", () => {
+      presetService.savePresets(presetType, presetTarget, []);
+
+      expect(userConfigStub.delete).toHaveBeenCalledTimes(1);
+      expect(userConfigStub.delete).toHaveBeenCalledWith(presetDictKey);
+      expect(userConfigStub.set).not.toHaveBeenCalled();
+    });
+
+    it("displays the success toast by default when saving presets", () => {
+      presetService.savePresets(presetType, presetTarget, [{ presetProperty: 
"v1" }]);
+      expect(messageStub.success).toHaveBeenCalledWith("Preset saved");
+    });
+
+    it("suppresses the toast when displayMessage is explicitly null", () => {
+      presetService.savePresets(presetType, presetTarget, [{ presetProperty: 
"v1" }], null);
+      expect(messageStub.success).not.toHaveBeenCalled();
+      expect(messageStub.error).not.toHaveBeenCalled();
+    });
+
+    it("createPreset appends to existing presets and writes back", () => {
+      const existing: Preset[] = [{ presetProperty: "v1" }];
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify(existing)));
+
+      presetService.createPreset(presetType, presetTarget, { presetProperty: 
"v2" });
+
+      expect(userConfigStub.set).toHaveBeenCalledWith(
+        presetDictKey,
+        JSON.stringify([{ presetProperty: "v1" }, { presetProperty: "v2" }])
+      );
+    });
+
+    it("createPreset does not write the preset back when it already exists", 
() => {
+      // The service throws inside an RxJS subscribe handler, so the throw is 
reported
+      // asynchronously and is not catchable with toThrow. Verify the no-op 
behaviorally.
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify([{ 
presetProperty: "v1" }])));
+
+      try {
+        presetService.createPreset(presetType, presetTarget, { presetProperty: 
"v1" });
+      } catch {
+        // swallow: the throw may surface synchronously depending on RxJS 
behavior, but
+        // we only care that no save happened.
+      }
+
+      expect(userConfigStub.set).not.toHaveBeenCalled();
+      expect(userConfigStub.delete).not.toHaveBeenCalled();
+    });
+
+    it("updatePreset does not write the preset back when the original preset 
is missing", () => {
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify([{ 
presetProperty: "v1" }])));
+
+      try {
+        presetService.updatePreset(
+          presetType,
+          presetTarget,
+          { presetProperty: "missing" },
+          { presetProperty: "v3" }
+        );
+      } catch {
+        // see createPreset note above.
+      }
+
+      expect(userConfigStub.set).not.toHaveBeenCalled();
+    });
+
+    it("deletePreset removes the matching preset via savePresets", () => {
+      const a: Preset = { presetProperty: "v1" };
+      const b: Preset = { presetProperty: "v2" };
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify([a, b])));
+
+      presetService.deletePreset(presetType, presetTarget, b);
+
+      expect(userConfigStub.set).toHaveBeenCalledWith(presetDictKey, 
JSON.stringify([a]));
+    });
+
+    it("deletePreset clears the dictionary entry when the last preset is 
removed", () => {
+      const only: Preset = { presetProperty: "v1" };
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify([only])));
+
+      presetService.deletePreset(presetType, presetTarget, only);
+
+      // savePresets routes empty arrays to delete(), not set().
+      expect(userConfigStub.delete).toHaveBeenCalledWith(presetDictKey);
+      expect(userConfigStub.set).not.toHaveBeenCalled();
+    });
+
+    it("getPresets returns the parsed preset array stored in user config", () 
=> {
+      const stored: Preset[] = [{ presetProperty: "v1" }];
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify(stored)));
+
+      let result: readonly Preset[] | undefined;
+      presetService.getPresets(presetType, presetTarget).subscribe(v => 
(result = v));
+      expect(result).toEqual(stored);
+    });
+
+    it("getPresets yields an empty array when no entry exists", () => {
+      userConfigStub.fetchKey.mockReturnValue(of(null));
+
+      let result: readonly Preset[] | undefined;
+      presetService.getPresets(presetType, presetTarget).subscribe(v => 
(result = v));
+      expect(result).toEqual([]);
+    });
+
+    it("getPresets emits an error when the stored value is not a valid preset 
array", () => {
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify([{ 
presetProperty: 42 }, "not-an-object"])));
+
+      let err: unknown;
+      // throws inside an rxjs map() — surface via the error subscriber, not 
toThrow.
+      presetService.getPresets(presetType, presetTarget).subscribe({
+        next: () => {},
+        error: e => (err = e),
+      });
+      expect(err).toBeInstanceOf(Error);
+      expect((err as Error).message).toMatch(/formatted incorrectly/);
+    });
+  });
+
+  describe("operator preset application", () => {
+    beforeEach(() => {
+      workflowActionService.addOperator(mockPresetEnabledPredicate, mockPoint);
+      
workflowActionService.setOperatorProperty(mockPresetEnabledPredicate.operatorID,
 {
+        presetProperty: "before",
+        normalProperty: "untouched",
+      });
+    });
+
+    it("does not set operator properties when applyPreset uses a non-operator 
type", () => {
+      presetService.applyPreset("notAnOperator", 
mockPresetEnabledPredicate.operatorID, { presetProperty: "applied" });
+
+      expect(
+        
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
+      ).toEqual({ presetProperty: "before", normalProperty: "untouched" });
+    });
+
+    it("merges preset values into operator properties when a valid preset is 
applied", () => {
+      presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, { presetProperty: "applied" });
+
+      // normalProperty is preserved because applyPreset merges, rather than 
replaces.
+      expect(
+        
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
+      ).toEqual({ presetProperty: "applied", normalProperty: "untouched" });
+    });
+
+    it("does not change operator properties when an invalid preset is 
applied", () => {
+      // The handler throws on invalid presets, but RxJS subject error 
reporting is
+      // asynchronous, so we observe the no-side-effect outcome instead of 
toThrow.
+      try {
+        presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, {
+          notAPresetProperty: "applied",
+        });
+      } catch {
+        // tolerate either sync or async error surfacing.
+      }
+
+      expect(
+        
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
+      ).toEqual({ presetProperty: "before", normalProperty: "untouched" });
+    });

Review Comment:
   `applyPreset` invalid-preset path throws inside `PresetService`'s 
`applyPresetStream.subscribe(next: ...)` handler. Errors thrown from RxJS 
`next` handlers are typically surfaced via the unhandled-error mechanism, so 
wrapping `applyPreset(...)` in try/catch here usually won't prevent an uncaught 
exception (and can make the suite fail/flaky). Capture the unhandled RxJS error 
(e.g., via `rxjs` `config.onUnhandledError`) and assert it was raised, or 
change the production code/test approach so invalid presets are reported 
without throwing from within `subscribe`.



##########
frontend/src/app/workspace/service/preset/preset.service.spec.ts:
##########
@@ -16,432 +16,435 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-// TODO: rewrite skipped tests away from Jasmine done/fail callbacks (#4861).
-// These stubs make the it.skip bodies type-check without running.
-declare function done(): void;
-declare function fail(message?: string): never;
-
-// TODO(vitest): done callbacks need rewrite to async/Promise pattern; these 
specs are skipped pending follow-up — tracked in #4861.
-
-// import { HttpClientTestingModule, HttpTestingController } from 
"@angular/common/http/testing";
-// import { TestBed, inject, fakeAsync, tick, flush, discardPeriodicTasks } 
from "@angular/core/testing";
-// import { BrowserAnimationsModule } from 
"@angular/platform-browser/animations";
-// import { NzMessageModule, NzMessageService } from "ng-zorro-antd/message";
-// import { AppSettings } from "src/app/common/app-setting";
-// import { DictionaryService } from 
"src/app/common/service/user/user-dictionary/dictionary.service";
-// import { JointUIService } from "../joint-ui/joint-ui.service";
-// import { OperatorMetadataService } from 
"../operator-metadata/operator-metadata.service";
-// import { StubOperatorMetadataService } from 
"../operator-metadata/stub-operator-metadata.service";
-// import { UndoRedoService } from "../undo-redo/undo-redo.service";
-// import { WorkflowActionService } from 
"../workflow-graph/model/workflow-action.service";
-// import { WorkflowUtilService } from 
"../workflow-graph/util/workflow-util.service";
-// import { PresetService } from "./preset.service";
-// import { mockPresetEnabledPredicate, mockPoint } from 
"../workflow-graph/model/mock-workflow-data";
-// import { CustomJSONSchema7 } from 
"../../types/custom-json-schema.interface";
-// import { mockPresetEnabledSchema } from 
"../operator-metadata/mock-operator-metadata.data";
-
-// describe("PresetService", () => {
-//   let presetService: PresetService;
-//   let httpMock: HttpTestingController;
-
-//   beforeEach(fakeAsync(() => {
-//     TestBed.configureTestingModule({
-//       providers: [
-//         PresetService,
-//         DictionaryService,
-//         WorkflowActionService,
-//         WorkflowUtilService,
-//         JointUIService,
-//         UndoRedoService,
-//         { provide: OperatorMetadataService, useClass: 
StubOperatorMetadataService },
-//       ],
-//       imports: [NzMessageModule, HttpClientTestingModule, 
BrowserAnimationsModule],
-//     });
-
-//     presetService = TestBed.inject(PresetService);
-//     httpMock = TestBed.inject(HttpTestingController);
-
-//     // handle dict initialization
-//     const testDict = { a: "a", b: "b", c: "c" };
-//     const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//     
httpMock.expectOne(`${AppSettings.getApiEndpoint()}/users/auth/status`).flush({ 
name: "testUser", uid: 1 }); // allow autologin by userService
-//     httpMock.expectOne(`${dictApiEndpoint}/get`).flush({ code: 1, result: 
testDict });
-//     httpMock.verify();
-//     tick();
-//   }));
-
-//   it("should be created", inject([WorkflowActionService], (injectedService: 
WorkflowActionService) => {
-//     expect(injectedService).toBeTruthy();
-//   }));
-
-//   describe("preset I/O", () => {
-//     it.skip("should emit an event when presets are applied", () => {
-//       presetService.applyPresetStream.subscribe(value => {
-//         expect(value).toEqual({ type: "testType", target: "testTarget", 
preset: { testPresetKey: "testPresetValue" } });
-//         done();
-//       });
-//       presetService.applyPreset("testType", "testTarget", { testPresetKey: 
"testPresetValue" });
-//     });
-
-//     it.skip("should emit an event when presets are saved", () => {
-//       presetService.savePresetsStream.subscribe(value => {
-//         expect(value).toEqual({
-//           type: "testType",
-//           target: "testTarget",
-//           presets: [{ testPresetKey: "testPresetValue" }],
-//         });
-//         done();
-//       });
-//       presetService.savePresets("testType", "testTarget", [{ testPresetKey: 
"testPresetValue" }]);
-//     });
-
-//     it("should save to user dictionary when presets are saved", 
fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-
-//       presetService.savePresets("testType", "testTarget", [{ testPresetKey: 
"testPresetValue" }]);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       expect(
-//         userDictionaryService.getUserDictionary()[`${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`]
-//       ).toEqual(JSON.stringify([{ testPresetKey: "testPresetValue" }]));
-//     }));
-
-//     it("should save amended entry to user dictionary when a preset is 
deleted", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//       const presetDictKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const initialPresets = [{ testPresetKey: "testPresetValue" }, { 
testPresetKey: "testPresetValue2" }];
-//       const endPresets = initialPresets.slice(0, 1);
-
-//       presetService.savePresets("testType", "testTarget", initialPresets);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(initialPresets));
-
-//       presetService.deletePreset("testType", "testTarget", 
initialPresets[1]);
-//       savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(endPresets));
-//     }));
-
-//     it("should save amended entry to user dictionary when a preset is 
updated", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-//       const presetDictKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const initialPresets = [{ testPresetKey: "testPresetValue" }, { 
testPresetKey: "testPresetValue2" }];
-//       const updatedPreset = { testPresetKey: "testPresetValue3" };
-//       const endPresets = [initialPresets[0], updatedPreset];
-
-//       presetService.savePresets("testType", "testTarget", initialPresets);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(initialPresets));
-
-//       presetService.updatePreset("testType", "testTarget", 
initialPresets[1], updatedPreset);
-//       savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/set`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("POST");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       flush();
-//       
expect(userDictionaryService.getUserDictionary()[presetDictKey]).toEqual(JSON.stringify(endPresets));
-//     }));
-
-//     it("should delete from dictionary service when empty preset list is 
saved", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-//       const dictApiEndpoint = 
`${AppSettings.getApiEndpoint()}/${DictionaryService.USER_DICTIONARY_ENDPOINT}`;
-
-//       presetService.savePresets("testType", "testTarget", []);
-//       let savePresetReq = httpMock.expectOne(`${dictApiEndpoint}/delete`);
-//       expect(savePresetReq.cancelled).toBeFalsy();
-//       expect(savePresetReq.request.method).toEqual("DELETE");
-//       expect(savePresetReq.request.responseType).toEqual("json");
-//       savePresetReq.flush({ code: 2, result: "arbitrary confirmation 
message" });
-//       httpMock.verify();
-//       tick();
-//       expect(
-//         userDictionaryService.getUserDictionary()[`${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`]
-//       ).toBeUndefined();
-//       flush();
-//     }));
-
-//     it("should get user presets from the user dictionary", fakeAsync(() => {
-//       const userDictionaryService = TestBed.inject(DictionaryService);
-
-//       // cant use an expression as a property name, so the dict must be 
setup via assignment :(
-//       const testPresetKey = `${(PresetService as 
any).DICT_PREFIX}-testType-testTarget`;
-//       const testPresets = [{ testPresetKey: "testPresetValue" }];
-//       const testDict: any = {};
-//       testDict[testPresetKey] = JSON.stringify(testPresets);
-
-//       vi.spyOn(userDictionaryService, 
"forceGetUserDictionary").mockReturnValue(testDict);
-
-//       presetService = new PresetService(
-//         userDictionaryService,
-//         TestBed.inject(NzMessageService),
-//         TestBed.inject(WorkflowActionService),
-//         TestBed.inject(OperatorMetadataService)
-//       );
-
-//       const presets = presetService.getPresets("testType", "testTarget");
-//       expect(presets).toEqual(testPresets);
-//     }));
-//   });
-
-//   describe("operator preset handling", () => {
-//     let workflowActionService: WorkflowActionService;
-
-//     beforeEach(() => {
-//       workflowActionService = TestBed.inject(WorkflowActionService);
-//       workflowActionService.addOperator(mockPresetEnabledPredicate, 
mockPoint);
-//       
workflowActionService.setOperatorProperty(mockPresetEnabledPredicate.operatorID,
 {
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     });
-
-//     it("should not set operator properties if a non operator preset is 
applied", fakeAsync(() => {
-//       presetService.applyPreset("NotAnOperator", "NotAnOperatorID", { 
NotAPresetProperty: "presetApplied" });
-//       tick();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-
-//     it("should not set operator properties if an invalid operator preset is 
applied", fakeAsync(() => {
-//       expect(() => {
-//         presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, {
-//           NotAPresetProperty: "presetApplied",
-//         });
-//         flush();
-//       }).toThrow();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "testPresetProperty",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-
-//     it("should set operator properties if a valid operator preset is 
applied", fakeAsync(() => {
-//       presetService.applyPreset("operator", 
mockPresetEnabledPredicate.operatorID, { presetProperty: "presetApplied" });
-//       tick();
-
-//       expect(
-//         
workflowActionService.getTexeraGraph().getOperator(mockPresetEnabledPredicate.operatorID).operatorProperties
-//       ).toEqual({
-//         presetProperty: "presetApplied",
-//         normalProperty: "testNormalProperty",
-//       });
-//     }));
-//   });
-
-//   describe("operator preset validation", () => {
-//     beforeEach(() => {
-//       const workflowActionService = TestBed.inject(WorkflowActionService);
-//       workflowActionService.addOperator(mockPresetEnabledPredicate, 
mockPoint);
-//     });
-
-//     it("should reject an empty preset", () => {
-//       expect(presetService.isValidOperatorPreset({}, 
mockPresetEnabledPredicate.operatorID)).toBe(false);
-//     });
-
-//     it("should reject preset with the wrong properties", () => {
-//       expect(
-//         presetService.isValidOperatorPreset(
-//           { wrongProperty: "wrongpropertyPreset" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(false);
-//     });
-
-//     it("should reject preset with empty properties", () => {
-//       expect(
-//         presetService.isValidOperatorPreset({ presetProperty: "" }, 
mockPresetEnabledPredicate.operatorID)
-//       ).toBe(false);
-//     });
-
-//     it("should accept a properly formatted preset", () => {
-//       expect(
-//         presetService.isValidOperatorPreset(
-//           { presetProperty: "presetHasBeenApplied" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(true);
-//     });
-
-//     it("should reject new presets if they already exist", () => {
-//       vi.spyOn(presetService, "getPresets").mockReturnValue([{ 
presetProperty: "presetHasBeenApplied" }]);
-
-//       expect(
-//         presetService.isValidNewOperatorPreset(
-//           { presetProperty: "presetHasBeenApplied" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(false);
-//     });
-
-//     it("should accept new presets if they are novel", () => {
-//       vi.spyOn(presetService, "getPresets").mockReturnValue([{ 
presetProperty: "presetHasBeenApplied" }]);
-
-//       expect(
-//         presetService.isValidNewOperatorPreset(
-//           { presetProperty: "alternatePreset" },
-//           mockPresetEnabledPredicate.operatorID
-//         )
-//       ).toBe(true);
-//     });
-//   });
-
-//   describe("operator preset schema generation", () => {
-//     it("should generate a preset schema", () => {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           presetProperty: {
-//             type: "string",
-//             description: "property that can be saved in presets",
-//             title: "presetProperty",
-//             "enable-presets": true,
-//           },
-//           normalProperty: {
-//             type: "string",
-//             description: "property that is excluded in presets",
-//             title: "normalProperty",
-//           },
-//         },
-//         required: ["normalProperty"],
-//       };
-
-//       const presetSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           presetProperty: {
-//             type: "string",
-//             description: "property that can be saved in presets",
-//             title: "presetProperty",
-//             "enable-presets": true,
-//           },
-//         },
-//         required: ["presetProperty"],
-//         additionalProperties: false,
-//       };
-
-//       
expect(PresetService.getOperatorPresetSchema(operatorSchema)).toEqual(presetSchema);
-//     });
-
-//     it("should throw an error if the operator schema has no properties", () 
=> {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {},
-//         required: ["normalProperty"],
-//       };
-
-//       expect(() => 
PresetService.getOperatorPresetSchema(operatorSchema)).toThrow();
-//     });
-
-//     it("should throw an error if the operator schema has no preset 
properties", () => {
-//       const operatorSchema = <CustomJSONSchema7>{
-//         type: "object",
-//         properties: {
-//           normalProperty: {
-//             type: "string",
-//             description: "property that is excluded in presets",
-//             title: "normalProperty",
-//           },
-//         },
-//         required: ["normalProperty"],
-//       };
-
-//       expect(() => 
PresetService.getOperatorPresetSchema(operatorSchema)).toThrow();
-//     });
-//   });
-
-//   describe("operator preset generation", () => {
-//     describe("getOperatorPreset - throw errors if invalid", () => {
-//       it("should throw an error if operator properties is empty", () => {
-//         expect(() => 
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, 
{})).toThrow();
-//       });
-
-//       it("should throw an error if operator properties doesn't have all the 
preset properties", () => {
-//         expect(() =>
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, { 
wrongProperty: "wrongpropertyPreset" })
-//         ).toThrow();
-//       });
-
-//       it("should return a preset if operator properties has all the preset 
properties", () => {
-//         expect(
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, { 
presetProperty: "presetPropertyValue" })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-
-//       it("should return a preset if operator properties has a superset of 
preset properties", () => {
-//         expect(
-//           
PresetService.getOperatorPreset(mockPresetEnabledSchema.jsonSchema, {
-//             presetProperty: "presetPropertyValue",
-//             otherProperty: "othervalue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-//     });
-
-//     describe("filterOperatorPresetProperties - doesn't guarantee preset is 
valid", () => {
-//       it("should never add to operator properties", () => {
-//         
expect(PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {})).toEqual({});
-//       });
-
-//       it("should filter out non preset properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             wrongProperty: "wrongpropertyPreset",
-//           })
-//         ).toEqual({});
-//       });
-
-//       it("should not filter out preset properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             presetProperty: "presetPropertyValue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-
-//       it("should filter out non preset properties and leave behind preset 
properties", () => {
-//         expect(
-//           
PresetService.filterOperatorPresetProperties(mockPresetEnabledSchema.jsonSchema,
 {
-//             presetProperty: "presetPropertyValue",
-//             otherProperty: "othervalue",
-//           })
-//         ).toEqual({ presetProperty: "presetPropertyValue" });
-//       });
-//     });
-//   });
-// });
+
+import { TestBed } from "@angular/core/testing";
+import { NzMessageService } from "ng-zorro-antd/message";
+import { of } from "rxjs";
+import { UserConfigService } from 
"src/app/common/service/user/config/user-config.service";
+import { CustomJSONSchema7 } from "../../types/custom-json-schema.interface";
+import { JointUIService } from "../joint-ui/joint-ui.service";
+import { mockPresetEnabledSchema } from 
"../operator-metadata/mock-operator-metadata.data";
+import { OperatorMetadataService } from 
"../operator-metadata/operator-metadata.service";
+import { StubOperatorMetadataService } from 
"../operator-metadata/stub-operator-metadata.service";
+import { UndoRedoService } from "../undo-redo/undo-redo.service";
+import { mockPoint, mockPresetEnabledPredicate } from 
"../workflow-graph/model/mock-workflow-data";
+import { WorkflowActionService } from 
"../workflow-graph/model/workflow-action.service";
+import { WorkflowUtilService } from 
"../workflow-graph/util/workflow-util.service";
+import { commonTestProviders } from "../../../common/testing/test-utils";
+import { Preset, PresetService } from "./preset.service";
+
+// Ajv 8 defaults to strict mode and rejects unknown keywords at compile time, 
so
+// `isValidOperatorPreset` (which compiles operator schemas containing the
+// 'enable-presets' marker) throws before it can validate. Register the keyword
+// once as a no-op so the validation paths are exercisable in tests.
+const ajvInstance = (PresetService as any).ajv;
+if (!ajvInstance.RULES.keywords["enable-presets"]) {
+  ajvInstance.addKeyword({ keyword: "enable-presets", schemaType: "boolean" });
+}
+
+describe("PresetService", () => {
+  let userConfigStub: {
+    fetchKey: ReturnType<typeof vi.fn>;
+    set: ReturnType<typeof vi.fn>;
+    delete: ReturnType<typeof vi.fn>;
+  };
+  let messageStub: {
+    success: ReturnType<typeof vi.fn>;
+    error: ReturnType<typeof vi.fn>;
+    info: ReturnType<typeof vi.fn>;
+    warning: ReturnType<typeof vi.fn>;
+  };
+  let presetService: PresetService;
+  let workflowActionService: WorkflowActionService;
+
+  const presetType = "operator";
+  const presetTarget = mockPresetEnabledPredicate.operatorType;
+  const presetDictKey = `${presetType}-${presetTarget}`;
+
+  beforeEach(() => {
+    userConfigStub = {
+      fetchKey: vi.fn().mockReturnValue(of(null)),
+      set: vi.fn().mockReturnValue(of(void 0)),
+      delete: vi.fn().mockReturnValue(of(void 0)),
+    };
+    messageStub = {
+      success: vi.fn(),
+      error: vi.fn(),
+      info: vi.fn(),
+      warning: vi.fn(),
+    };
+
+    TestBed.configureTestingModule({
+      providers: [
+        PresetService,
+        WorkflowActionService,
+        WorkflowUtilService,
+        JointUIService,
+        UndoRedoService,
+        { provide: OperatorMetadataService, useClass: 
StubOperatorMetadataService },
+        { provide: UserConfigService, useValue: userConfigStub },
+        { provide: NzMessageService, useValue: messageStub },
+        ...commonTestProviders,
+      ],
+    });
+
+    presetService = TestBed.inject(PresetService);
+    workflowActionService = TestBed.inject(WorkflowActionService);
+  });
+
+  it("should be created", () => {
+    expect(presetService).toBeTruthy();
+  });
+
+  describe("preset I/O", () => {
+    it("emits an event on applyPresetStream when a preset is applied", () => {
+      const seen: any[] = [];
+      const sub = presetService.applyPresetStream.subscribe(value => 
seen.push(value));
+
+      const preset: Preset = { presetProperty: "applied" };
+      presetService.applyPreset("nonOperatorType", "anyTarget", preset);
+
+      expect(seen).toEqual([{ type: "nonOperatorType", target: "anyTarget", 
preset }]);
+      sub.unsubscribe();
+    });
+
+    it("emits an event on savePresetsStream when presets are saved", () => {
+      const seen: any[] = [];
+      const sub = presetService.savePresetsStream.subscribe(value => 
seen.push(value));
+
+      const presets: Preset[] = [{ presetProperty: "v1" }];
+      presetService.savePresets(presetType, presetTarget, presets);
+
+      expect(seen).toEqual([{ type: presetType, target: presetTarget, presets 
}]);
+      sub.unsubscribe();
+    });
+
+    it("writes through UserConfigService.set when saving a non-empty preset 
list", () => {
+      const presets: Preset[] = [{ presetProperty: "v1" }];
+      presetService.savePresets(presetType, presetTarget, presets);
+
+      expect(userConfigStub.set).toHaveBeenCalledTimes(1);
+      expect(userConfigStub.set).toHaveBeenCalledWith(presetDictKey, 
JSON.stringify(presets));
+      expect(userConfigStub.delete).not.toHaveBeenCalled();
+    });
+
+    it("calls UserConfigService.delete instead of set when saving an empty 
preset list", () => {
+      presetService.savePresets(presetType, presetTarget, []);
+
+      expect(userConfigStub.delete).toHaveBeenCalledTimes(1);
+      expect(userConfigStub.delete).toHaveBeenCalledWith(presetDictKey);
+      expect(userConfigStub.set).not.toHaveBeenCalled();
+    });
+
+    it("displays the success toast by default when saving presets", () => {
+      presetService.savePresets(presetType, presetTarget, [{ presetProperty: 
"v1" }]);
+      expect(messageStub.success).toHaveBeenCalledWith("Preset saved");
+    });
+
+    it("suppresses the toast when displayMessage is explicitly null", () => {
+      presetService.savePresets(presetType, presetTarget, [{ presetProperty: 
"v1" }], null);
+      expect(messageStub.success).not.toHaveBeenCalled();
+      expect(messageStub.error).not.toHaveBeenCalled();
+    });
+
+    it("createPreset appends to existing presets and writes back", () => {
+      const existing: Preset[] = [{ presetProperty: "v1" }];
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify(existing)));
+
+      presetService.createPreset(presetType, presetTarget, { presetProperty: 
"v2" });
+
+      expect(userConfigStub.set).toHaveBeenCalledWith(
+        presetDictKey,
+        JSON.stringify([{ presetProperty: "v1" }, { presetProperty: "v2" }])
+      );
+    });
+
+    it("createPreset does not write the preset back when it already exists", 
() => {
+      // The service throws inside an RxJS subscribe handler, so the throw is 
reported
+      // asynchronously and is not catchable with toThrow. Verify the no-op 
behaviorally.
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify([{ 
presetProperty: "v1" }])));
+
+      try {
+        presetService.createPreset(presetType, presetTarget, { presetProperty: 
"v1" });
+      } catch {
+        // swallow: the throw may surface synchronously depending on RxJS 
behavior, but
+        // we only care that no save happened.
+      }
+
+      expect(userConfigStub.set).not.toHaveBeenCalled();
+      expect(userConfigStub.delete).not.toHaveBeenCalled();
+    });
+
+    it("updatePreset does not write the preset back when the original preset 
is missing", () => {
+      userConfigStub.fetchKey.mockReturnValue(of(JSON.stringify([{ 
presetProperty: "v1" }])));
+
+      try {
+        presetService.updatePreset(
+          presetType,
+          presetTarget,
+          { presetProperty: "missing" },
+          { presetProperty: "v3" }
+        );
+      } catch {
+        // see createPreset note above.
+      }
+
+      expect(userConfigStub.set).not.toHaveBeenCalled();
+    });

Review Comment:
   Same issue as the duplicate-create case: `updatePreset` throws from inside 
an RxJS `subscribe` callback when the original preset is missing. The try/catch 
here likely won't catch it, so this spec can produce an unhandled exception and 
fail the entire run. Consider capturing RxJS unhandled errors and asserting on 
the error content, or restructure the test to avoid triggering a `throw` inside 
`subscribe` without an error handler.



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