From b924ae5f0d21385588e33fe7113a12a9e3080a1f Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Wed, 1 Jul 2026 09:13:20 +0200 Subject: [PATCH 1/3] test: fix flaky DSDynamicTypeBindRelationService spec (double spyOn) The FormBuilderService mock (getMockFormBuilderService) is a jasmine.createSpyObj, so getTypeBindModel / getTypeBindModelUpdates are already spies. Several specs then call spyOn(...) on them again, which Jasmine rejects with " : getTypeBindModel has already been spied upon". Under Karma/Jasmine's randomized spec order this surfaced intermittently, so the same commit passed some CI runs and failed others. Reconfigure the existing spies via (spy as jasmine.Spy).and.returnValue/callFake(...) instead of re-spying them (the standard idiom for a createSpyObj mock). The spy behaviour per test is identical, so all assertions are unchanged; the illegal double-spyOn is removed, making "already been spied upon" impossible in any order. Co-Authored-By: Claude Opus 4.8 --- ...-dynamic-type-bind-relation.service.spec.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts index 7311d19e949..bd1e81f55a7 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts @@ -86,7 +86,7 @@ describe('DSDynamicTypeBindRelationService test suite', () => { it('Should not push undefined bind models', () => { const testModel = mockInputWithTypeBindModel; testModel.typeBindRelations = getTypeBindRelations(['boundType']); - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.returnValue(undefined); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.returnValue(undefined); const relatedModels = service.getRelatedFormModel(testModel); @@ -143,7 +143,7 @@ describe('DSDynamicTypeBindRelationService test suite', () => { opposingMatch: HIDDEN_MATCHER.match, onChange: jasmine.createSpy('onChange') }; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.returnValue(undefined); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.returnValue(undefined); const hasMatch = service.matchesCondition(relation, visibleMatcher); @@ -159,7 +159,7 @@ describe('DSDynamicTypeBindRelationService test suite', () => { opposingMatch: MATCH_VISIBLE, onChange: jasmine.createSpy('onChange') }; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.returnValue(undefined); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.returnValue(undefined); const hasMatch = service.matchesCondition(relation, hiddenMatcher); @@ -185,8 +185,8 @@ describe('DSDynamicTypeBindRelationService test suite', () => { onChange: jasmine.createSpy('onChange') }; (service as any).dynamicMatchers = [visibleMatcher]; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.callFake(() => bindModelAvailable ? bindModel : undefined); - spyOn((service as any).formBuilderService, 'getTypeBindModelUpdates').and.returnValue(bindModelUpdates$.asObservable()); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.callFake(() => bindModelAvailable ? bindModel : undefined); + ((service as any).formBuilderService.getTypeBindModelUpdates as jasmine.Spy).and.returnValue(bindModelUpdates$.asObservable()); const subscriptions = service.subscribeRelations(testModel, dcTypeControl); expect(subscriptions.length).toBe(1); @@ -220,8 +220,8 @@ describe('DSDynamicTypeBindRelationService test suite', () => { onChange: jasmine.createSpy('onChange') }; (service as any).dynamicMatchers = [hiddenMatcher]; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.callFake(() => bindModelAvailable ? bindModel : undefined); - spyOn((service as any).formBuilderService, 'getTypeBindModelUpdates').and.returnValue(bindModelUpdates$.asObservable()); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.callFake(() => bindModelAvailable ? bindModel : undefined); + ((service as any).formBuilderService.getTypeBindModelUpdates as jasmine.Spy).and.returnValue(bindModelUpdates$.asObservable()); const subscriptions = service.subscribeRelations(testModel, dcTypeControl); expect(subscriptions.length).toBe(1); @@ -254,8 +254,8 @@ describe('DSDynamicTypeBindRelationService test suite', () => { onChange: jasmine.createSpy('onChange') }; (service as any).dynamicMatchers = [visibleMatcher]; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.callFake(() => bindModelAvailable ? bindModel : undefined); - spyOn((service as any).formBuilderService, 'getTypeBindModelUpdates').and.returnValue(bindModelUpdates$.asObservable()); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.callFake(() => bindModelAvailable ? bindModel : undefined); + ((service as any).formBuilderService.getTypeBindModelUpdates as jasmine.Spy).and.returnValue(bindModelUpdates$.asObservable()); const subscriptions = service.subscribeRelations(testModel, dcTypeControl); expect(visibleMatcher.onChange).toHaveBeenCalledWith(false, testModel, dcTypeControl, jasmine.anything()); From 1a3175497ff12349d674a233ad02bb19a264c768 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Wed, 1 Jul 2026 09:18:11 +0200 Subject: [PATCH 2/3] test: await TestBed setup in DSDynamicTypeBindRelationService spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: the first beforeEach did `TestBed.configureTestingModule({...}).compileComponents().then();` — the promise was never returned/awaited, so nothing guaranteed the module (and its fresh getMockFormBuilderService() spy mock) was fully set up before each spec, which is the kind of async setup slop that makes the same commit pass some CI runs and fail others. Return the promise so Jasmine awaits it and every spec starts from a fresh, fully-configured module. Complements the spy-reconfiguration fix. Co-Authored-By: Claude Opus 4.8 --- .../ds-dynamic-type-bind-relation.service.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts index bd1e81f55a7..932375c2072 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts @@ -26,7 +26,10 @@ describe('DSDynamicTypeBindRelationService test suite', () => { let injector: Injector; beforeEach(() => { - TestBed.configureTestingModule({ + // Return the promise so the module (and its fresh getMockFormBuilderService() spy mock) is fully set up + // before each spec runs; the previous dangling .then() did not await, allowing async setup slop between + // specs. + return TestBed.configureTestingModule({ imports: [ReactiveFormsModule], providers: [ { provide: FormBuilderService, useValue: getMockFormBuilderService() }, @@ -34,7 +37,7 @@ describe('DSDynamicTypeBindRelationService test suite', () => { { provide: DynamicFormRelationService }, DISABLED_MATCHER_PROVIDER, HIDDEN_MATCHER_PROVIDER, REQUIRED_MATCHER_PROVIDER ] - }).compileComponents().then(); + }).compileComponents(); }); beforeEach(inject([DsDynamicTypeBindRelationService, DynamicFormRelationService], From 4a392fdf083974589ed90bbe977903cabb3fd42c Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Wed, 1 Jul 2026 11:44:48 +0200 Subject: [PATCH 3/3] test: reword beforeEach comment (address Copilot review) Replace the colloquial "async setup slop" wording with a clearer description ("letting asynchronous module setup leak between specs"). Comment-only change. Co-Authored-By: Claude Opus 4.8 --- .../ds-dynamic-type-bind-relation.service.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts index 932375c2072..9faec7dd7d4 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts @@ -27,8 +27,8 @@ describe('DSDynamicTypeBindRelationService test suite', () => { beforeEach(() => { // Return the promise so the module (and its fresh getMockFormBuilderService() spy mock) is fully set up - // before each spec runs; the previous dangling .then() did not await, allowing async setup slop between - // specs. + // before each spec runs; the previous dangling .then() did not await, letting asynchronous module setup + // leak between specs. return TestBed.configureTestingModule({ imports: [ReactiveFormsModule], providers: [