From fe5c6f182e8118f21f611e7198f5f5ec8effdcb7 Mon Sep 17 00:00:00 2001 From: Maurice <61554723+mauriceboe@users.noreply.github.com> Date: Tue, 25 Aug 2026 17:09:29 +0200 Subject: [PATCH] fix(ci): restore the peer dep the swc install drops, and close a test race (#2040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two separate reasons v4.0.0 (#1719) is red. The SonarQube workflow killed all 616 client test files with "Cannot find module '@testing-library/dom'". That package is a peer dependency of @testing-library/react and not a direct dependency of the client, and the step that installs @swc/core's Linux binary passes --legacy-peer-deps, which tells npm not to put peer dependencies back when it re-resolves the tree. In test.yml that step lives in the server job, which never runs client tests, so folding the jobs into one (#2039) is what brought the two together. It now installs alongside the swc binary, with its version read from the peer range rather than pinned, and a one-line resolve check right after fails fast with the module name instead of six hundred identical failures twenty minutes later. MAdminStoragePanel's FE-MOB-MSTOR-015 was a genuine race in the test, not in the panel. Its POST handler incremented postCount, then awaited request.json(), and only then pushed to migrationPosts — so between the two there was a window where one POST had been counted but migrationPosts was still empty. The GET handler reports `migrations: []` for an empty migrationPosts, i.e. "nothing running", so a poll landing in that window told the queue it was free to start the second migration, and the waitFor(postCount === 1) above could resolve inside it too. Parsing before counting closes it. Verified by running the whole mobile suite at --maxWorkers=8, where it used to surface. --- .github/workflows/sonarqube.yml | 21 ++++++++++++++++++- .../mobile/admin/MAdminStoragePanel.test.tsx | 10 ++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 8d4d5177b..6caba32ab 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -45,9 +45,28 @@ jobs: # Same reason as test.yml: the lockfile was generated on Windows and omits # @swc/core's Linux optional native binary, so npm ci skips it on the # runner and the server's SWC transform cannot load. + # + # @testing-library/dom rides along, and has to. This install re-resolves + # the tree, and --legacy-peer-deps means npm does not put peer + # dependencies back — @testing-library/dom is a peer of + # @testing-library/react and not a direct dependency of the client, so it + # was silently dropped and all 616 client test files died on + # "Cannot find module". In test.yml this step lives in the server job, + # which never runs client tests, so the collision only appears once the + # jobs are folded into one. The version is read from the peer range rather + # than pinned here, so a bump to @testing-library/react carries over. run: | SWC_VERSION=$(node -p "require('@swc/core/package.json').version") - npm install --no-save --legacy-peer-deps "@swc/core-linux-x64-gnu@$SWC_VERSION" + DOM_RANGE=$(node -p "require('@testing-library/react/package.json').peerDependencies['@testing-library/dom']") + npm install --no-save --legacy-peer-deps \ + "@swc/core-linux-x64-gnu@$SWC_VERSION" \ + "@testing-library/dom@$DOM_RANGE" + + - name: Verify the test toolchain survived that install + # Cheap tripwire: if a future dependency bump loses another peer the same + # way, this fails here with the name of the module instead of 600 + # identical test failures twenty minutes later. + run: node -e "require.resolve('@testing-library/dom'); require.resolve('@swc/core')" - name: Build shared # Only shared, not the root `build`. server and client run their suites diff --git a/client/tests/unit/mobile/admin/MAdminStoragePanel.test.tsx b/client/tests/unit/mobile/admin/MAdminStoragePanel.test.tsx index 12ffcda04..bc2b05dcb 100644 --- a/client/tests/unit/mobile/admin/MAdminStoragePanel.test.tsx +++ b/client/tests/unit/mobile/admin/MAdminStoragePanel.test.tsx @@ -329,8 +329,16 @@ describe('MAdminStoragePanel', () => { server.use( http.put('/api/admin/storage', () => HttpResponse.json(savedState)), http.post('/api/admin/storage/migrations', async ({ request }) => { - postCount += 1; + // Parse BEFORE counting, so postCount and migrationPosts move together. + // With the increment first, the await left a window where postCount was + // already 1 while migrationPosts was still empty — and the GET handler + // below reports `migrations: []` for an empty migrationPosts, i.e. "no + // migration running". A poll landing in that window told the queue it was + // free to dequeue the second candidate, and the `waitFor(postCount === 1)` + // further down could resolve inside it too. Rare locally, reliable under + // CI load, where the microtask gap is wider than the 50ms test poll. const body = (await request.json()) as { category: StorageCategory; to: string }; + postCount += 1; migrationPosts.push(body); // Pins the single-flight fix: the second POST must never arrive // before the first migration is server-confirmed terminal — a