From 9bb0aa1c493e125c8513664cb217ce3f626e2528 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 30 Mar 2026 18:17:16 +0200 Subject: [PATCH] Enable concurrent vitest execution (#36998) Enable [`sequence.concurrent`](https://vitest.dev/config/sequence.html#sequence-concurrent) to run all js tests in parallel. This will help catch potential concurrency bugs in the future. The "Repository Branch Settings" test was not concurrency-safe, it was refactored to remove shared mutable state. Co-Authored-By: Claude (claude-opus-4-6) --------- Co-authored-by: Claude (claude-opus-4-6) --- vitest.config.ts | 3 + .../features/repo-settings-branches.test.ts | 76 ++++++++----------- web_src/js/markup/render-iframe.test.ts | 2 + 3 files changed, 35 insertions(+), 46 deletions(-) diff --git a/vitest.config.ts b/vitest.config.ts index e72ecf50eb..d2d3eeef1a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -14,6 +14,9 @@ export default defineConfig({ globals: true, watch: false, isolate: false, + sequence: { + concurrent: true, + }, }, plugins: [ stringPlugin(), diff --git a/web_src/js/features/repo-settings-branches.test.ts b/web_src/js/features/repo-settings-branches.test.ts index af3ab0438e..7f34781bcb 100644 --- a/web_src/js/features/repo-settings-branches.test.ts +++ b/web_src/js/features/repo-settings-branches.test.ts @@ -1,8 +1,7 @@ import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts'; import {POST} from '../modules/fetch.ts'; import {createSortable} from '../modules/sortable.ts'; -import type {SortableEvent, SortableOptions} from 'sortablejs'; -import type Sortable from 'sortablejs'; +import type {SortableEvent} from 'sortablejs'; vi.mock('../modules/fetch.ts', () => ({ POST: vi.fn(), @@ -12,63 +11,48 @@ vi.mock('../modules/sortable.ts', () => ({ createSortable: vi.fn(), })); +const branchesHTML = ` +
+
+
+
+
+
+
+
+
+
+
+`; + describe('Repository Branch Settings', () => { - beforeEach(() => { - document.body.innerHTML = ` -
-
-
-
-
-
-
-
-
-
-
- `; - - vi.clearAllMocks(); - }); - test('should initialize sortable for protected branches list', () => { + document.body.innerHTML = branchesHTML; + const callsBefore = vi.mocked(createSortable).mock.calls.length; initRepoSettingsBranchesDrag(); - - expect(createSortable).toHaveBeenCalledWith( - document.querySelector('#protected-branches-list'), - expect.objectContaining({ - handle: '.drag-handle', - animation: 150, - }), - ); + const newCalls = vi.mocked(createSortable).mock.calls.slice(callsBefore); + expect(newCalls).toHaveLength(1); + expect(newCalls[0][0]).toBe(document.querySelector('#protected-branches-list')); + expect(newCalls[0][1]).toMatchObject({handle: '.drag-handle', animation: 150}); }); test('should not initialize if protected branches list is not present', () => { - document.body.innerHTML = ''; - + document.querySelector('#protected-branches-list')?.remove(); + const callsBefore = vi.mocked(createSortable).mock.calls.length; initRepoSettingsBranchesDrag(); - - expect(createSortable).not.toHaveBeenCalled(); + expect(vi.mocked(createSortable).mock.calls.length).toBe(callsBefore); }); - test('should post new order after sorting', async () => { + test('should post new order after sorting', () => { + document.body.innerHTML = branchesHTML; vi.mocked(POST).mockResolvedValue({ok: true} as Response); - - // Mock createSortable to capture and execute the onEnd callback - vi.mocked(createSortable).mockImplementation(async (_el: HTMLElement, options: SortableOptions | undefined) => { - if (options?.onEnd) { - options.onEnd(new Event('SortableEvent') as SortableEvent); - } - return {destroy: vi.fn()} as unknown as Sortable; - }); - + const callsBefore = vi.mocked(createSortable).mock.calls.length; initRepoSettingsBranchesDrag(); - + const onEnd = vi.mocked(createSortable).mock.calls[callsBefore][1]!.onEnd!; + onEnd(new Event('SortableEvent') as SortableEvent); expect(POST).toHaveBeenCalledWith( 'some/repo/branches/priority', - expect.objectContaining({ - data: {ids: [1, 2, 3]}, - }), + expect.objectContaining({data: {ids: [1, 2, 3]}}), ); }); }); diff --git a/web_src/js/markup/render-iframe.test.ts b/web_src/js/markup/render-iframe.test.ts index 53c9dc3720..bdbe523cdf 100644 --- a/web_src/js/markup/render-iframe.test.ts +++ b/web_src/js/markup/render-iframe.test.ts @@ -28,6 +28,7 @@ describe('navigateToIframeLink', () => { }); test('unsafe links', () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined); window.location.href = 'http://localhost:3000/'; // eslint-disable-next-line no-script-url @@ -41,6 +42,7 @@ describe('navigateToIframeLink', () => { expect(openSpy).toHaveBeenCalledTimes(0); expect(assignSpy).toHaveBeenCalledTimes(0); expect(window.location.href).toBe('http://localhost:3000/'); + errorSpy.mockRestore(); vi.clearAllMocks(); }); });