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]