From 27bbfe4c25483d80b5b5eeb70c74b0ab253cf61d Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 18 Sep 2026 11:45:30 +0100 Subject: [PATCH 1/6] Align the Vitest browser packages at 4.1.11 `vitest` resolved to 4.1.11 while `@vitest/browser` stayed at 4.1.10, so every run opened with a "Running mixed versions is not supported" warning. --- frontend/package-lock.json | 2 +- frontend/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dc2d63e2be89..2d1e17ac975a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -163,7 +163,7 @@ "@types/urijs": "^1.19.26", "@typescript-eslint/eslint-plugin": "8.69.0", "@typescript-eslint/parser": "8.69.0", - "@vitest/browser-playwright": "^4.1.10", + "@vitest/browser-playwright": "^4.1.11", "@vitest/coverage-v8": "^4.1.11", "@vitest/eslint-plugin": "^1.6.27", "angular-eslint": "^22.2.0", diff --git a/frontend/package.json b/frontend/package.json index 2fb9f22b1363..f31357f34d39 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,7 +33,7 @@ "@types/urijs": "^1.19.26", "@typescript-eslint/eslint-plugin": "8.69.0", "@typescript-eslint/parser": "8.69.0", - "@vitest/browser-playwright": "^4.1.10", + "@vitest/browser-playwright": "^4.1.11", "@vitest/coverage-v8": "^4.1.11", "@vitest/eslint-plugin": "^1.6.27", "angular-eslint": "^22.2.0", From f9b248562cc2be0ece5529076ad333c4499e113d Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 15 Sep 2026 18:49:03 +0100 Subject: [PATCH 2/6] Keep Lit's dev-mode notice out of spec output Lit prints the notice once per test realm, so it repeated across the run without telling anyone anything new. --- frontend/src/test-browser-polyfills.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/src/test-browser-polyfills.ts b/frontend/src/test-browser-polyfills.ts index e236a5d72211..366a4a6f146f 100644 --- a/frontend/src/test-browser-polyfills.ts +++ b/frontend/src/test-browser-polyfills.ts @@ -33,3 +33,12 @@ const globalWithProcess = globalThis as unknown as { process?:{ env:Record }; + +globalWithLitWarnings.litIssuedWarnings ??= new Set([ + 'dev-mode', + 'Lit is in dev mode. Not recommended for production! See https://lit.dev/msg/dev-mode for more information.', +]); From fccc7952a76fc3f6fbd266bba70fb9d76f664738 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 15 Sep 2026 18:51:13 +0100 Subject: [PATCH 3/6] Silence ng-select's NG0953 in the debounce spec ng-select's panel measurement emits after the panel is destroyed, which flooded the run with two dozen warnings from a single spec. --- .../op-autocompleter/op-autocompleter.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts index 29f00c40d48e..45492572abc7 100644 --- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts +++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts @@ -438,6 +438,7 @@ describe('autocompleter', () => { }); it('should load items with debounce', async () => { + silenceDestroyedOutputWarning(); fixture.detectChanges(); // Wait for ngAfterViewInit's internal setTimeout(25ms) and debounce to fire. @@ -478,13 +479,12 @@ describe('autocompleter', () => { }); }); -// NG0953 ("Unexpected emit for destroyed OutputRef") is emitted when ng-select -// emits on an OutputRef during fixture teardown under fake timers. It is a real -// lifecycle smell, not pure noise — silencing it here is a pragmatic stopgap so -// these specs stay readable, not a fix. The proper fix is to stop the -// emit-after-destroy in the component teardown path; until then this is -// scoped to the two affected specs and passes every other warning through so it -// does not hide unrelated regressions. Do not promote this to a global filter. +// NG0953 ("Unexpected emit for destroyed OutputRef") comes from ng-select's +// dropdown panel: `_measureDimensions` retries through a Promise and +// requestAnimationFrame chain with no destroyed guard, so it emits after the +// panel is torn down (https://github.com/ng-select/ng-select/issues/2869). +// Silencing is a stopgap until ng-select guards that chain; it passes every +// other warning through. Do not promote this to a global filter. function silenceDestroyedOutputWarning():void { const originalWarn = console.warn.bind(console); From 40703de1743ce157e7ee288d390b223441ef8647 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 15 Sep 2026 18:51:13 +0100 Subject: [PATCH 4/6] Assert the errors that error-path specs expect The logged errors were part of the behavior under test, so they printed as noise instead of being checked. --- .../autocompleter/op-autocompleter/op-autocompleter.spec.ts | 5 ++++- .../work-packages/activities-tab/polling.controller.spec.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts index 45492572abc7..11fba085b06c 100644 --- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts +++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts @@ -251,11 +251,13 @@ describe('autocompleter', () => { }); it('should recover and keep loading results after a lookup fails', () => { + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined); + const lookupError = new Error('backend rejected the query'); vi.useFakeTimers(); try { getOptionsFnSpy.mockImplementation((searchTerm:string) => { if (searchTerm === 'bad') { - return throwError(() => new Error('backend rejected the query')); + return throwError(() => lookupError); } return of(workPackagesStub).pipe(map((wps) => wps.filter((wp) => searchTerm !== '' && wp.subject.includes(searchTerm)))); @@ -279,6 +281,7 @@ describe('autocompleter', () => { fixture.detectChanges(); expect(getOptionsFnSpy).toHaveBeenCalledWith('bad'); + expect(consoleError).toHaveBeenCalledWith(lookupError); expect(select.itemsList.items.length).toEqual(0); inputElement.value = 'Wor'; diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/polling.controller.spec.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/polling.controller.spec.ts index 5fd953099765..2edfa830738f 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/polling.controller.spec.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/polling.controller.spec.ts @@ -164,13 +164,16 @@ describe('Activities tab polling controller', () => { }); it('keeps polling after an error without an HTTP status', async () => { + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined); + const networkError = new Error('network hiccup'); await renderPolling(); - request.mockRejectedValue(new Error('network hiccup')); + request.mockRejectedValue(networkError); resolveContext(pluginContext()); await vi.advanceTimersByTimeAsync(20000); expect(request).toHaveBeenCalledTimes(2); + expect(consoleError).toHaveBeenCalledWith('Error updating activities list:', networkError); }); }); }); From 9977441b032dcafd75c960b0ad30a932b5131105 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 15 Sep 2026 18:51:59 +0100 Subject: [PATCH 5/6] Decline the unsaved-changes prompt explicitly The specs relied on Vitest answering an unmocked `confirm()` with false, which it announces on every call. --- .../global-edit-form-changes-tracker.service.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/app/shared/components/fields/edit/services/global-edit-form-changes-tracker/global-edit-form-changes-tracker.service.spec.ts b/frontend/src/app/shared/components/fields/edit/services/global-edit-form-changes-tracker/global-edit-form-changes-tracker.service.spec.ts index 5e361d44607c..826e7f661944 100644 --- a/frontend/src/app/shared/components/fields/edit/services/global-edit-form-changes-tracker/global-edit-form-changes-tracker.service.spec.ts +++ b/frontend/src/app/shared/components/fields/edit/services/global-edit-form-changes-tracker/global-edit-form-changes-tracker.service.spec.ts @@ -53,6 +53,7 @@ describe('GlobalEditFormChangesTrackerService', () => { beforeEach(() => { originalOpenProject = window.OpenProject; window.OpenProject = new OpenProject(); + vi.spyOn(window, 'confirm').mockReturnValue(false); TestBed.configureTestingModule({}); service = TestBed.inject(GlobalEditFormChangesTrackerService); }); @@ -61,6 +62,7 @@ describe('GlobalEditFormChangesTrackerService', () => { // eslint-disable-next-line @typescript-eslint/dot-notation service['abortController'].abort(); window.OpenProject = originalOpenProject; + vi.restoreAllMocks(); }); it('should be created', () => { From 4e13c3e8cd3c4e33e5c786432b233185f3de2266 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 15 Sep 2026 18:52:39 +0100 Subject: [PATCH 6/6] Keep sortable-lists spec host IDs distinct Two host components share a template and have no selector, so Angular generated the same component ID for both and warned about the collision. --- .../sortable-lists/sortable-lists.directive.behavior.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/shared/directives/sortable-lists/sortable-lists.directive.behavior.spec.ts b/frontend/src/app/shared/directives/sortable-lists/sortable-lists.directive.behavior.spec.ts index cd78031781fc..a409c05b678b 100644 --- a/frontend/src/app/shared/directives/sortable-lists/sortable-lists.directive.behavior.spec.ts +++ b/frontend/src/app/shared/directives/sortable-lists/sortable-lists.directive.behavior.spec.ts @@ -635,6 +635,7 @@ describe('sortable-lists directive group behavior', () => { describe('root teardown', () => { @Component({ + selector: 'op-sortable-lists-teardown-host', imports: [OpSortableListsDirective, OpSortableListsListDirective, OpSortableListsItemDirective], template: `