Skip to content

Update vitest monorepo to v4.1.0#478

Open
9larsons wants to merge 6 commits intomainfrom
vitest-v4-upgrade
Open

Update vitest monorepo to v4.1.0#478
9larsons wants to merge 6 commits intomainfrom
vitest-v4-upgrade

Conversation

@9larsons
Copy link
Collaborator

Summary

  • update vitest and @vitest/coverage-v8 to 4.1.0
  • regenerate yarn.lock
  • add/update targeted tests and small supporting code paths for Vitest v4 coverage behavior
  • remove unhandled-rejection noise in @tryghost/job-manager tests under Vitest v4

Testing

  • yarn test

Notes

@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8bebd2f3-0a89-4d58-9196-6204f66eef11

📥 Commits

Reviewing files that changed from the base of the PR and between e07053e and e4398b3.

📒 Files selected for processing (1)
  • vitest.config.ts

Walkthrough

This PR modifies CI scripts and upgrades Vitest packages in package.json, tightens Vitest coverage to include only **/lib/**, and enables "analytics": true in nx.json. It makes config eager-initialized at module load, adds two public DomainEvents test utilities (setTrackingEnabledForTest, resetTrackingStateForTest), adds/updates tests across multiple packages (bookshelf-pagination, config, domain-events, job-manager, metrics, request, express-test), and introduces explicit control-flow branches in the request implementation. No existing production API signatures were otherwise removed or altered.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: upgrading vitest and related dependencies to v4.1.0, which is the primary objective of the changeset.
Description check ✅ Passed The description clearly relates to the changeset, detailing the vitest upgrade, supporting test additions, and other modifications necessary for v4 compatibility.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vitest-v4-upgrade

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
packages/job-manager/test/job-manager.test.js (1)

467-467: Avoid swallowing all removeJob failures in this test path.

Line 467 catches every rejection, which can hide real regressions. Please narrow this to expected failure(s) only.

Suggested tightening
-                await jobManager.removeJob('failed-oneoff').catch(() => {});
+                await jobManager.removeJob('failed-oneoff').catch((err) => {
+                    assert.match(err.message, /not found|does not exist|missing/i);
+                });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/job-manager/test/job-manager.test.js` at line 467, The test
currently swallows all rejections from jobManager.removeJob('failed-oneoff')
which can hide failures; change the test to only accept the expected failure
instead of catching every error—either assert the promise rejects with the
specific error/message using your test framework's rejects/toThrow (targeting
jobManager.removeJob) or catch and rethrow unexpected errors by matching the
error type/message (e.g., only silence a known "JobNotFound" or
"CannotRemoveRunningJob" error), so the test fails on real regressions while
tolerating the intended failure.
packages/domain-events/test/DomainEvents.test.js (1)

91-93: Consider adding a comment explaining the sleep(0) usage.

When tracking is disabled, allSettled() resolves immediately (since #processedCount >= #dispatchCount`` is 0 >= 0). The `sleep(0)` before it is what actually allows the async-wrapped handler to complete. A brief comment would clarify this subtlety.

📝 Suggested comment
     DomainEvents.dispatch(new TestEvent('No tracking'));
+    // sleep(0) allows the async-wrapped handler to complete since
+    // allSettled() resolves immediately when tracking is disabled
     await sleep(0);
     await DomainEvents.allSettled();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/domain-events/test/DomainEvents.test.js` around lines 91 - 93, The
test relies on an async-wrapped handler completing before calling
DomainEvents.allSettled(), but the sleep(0) call is subtle and deserves an
explanatory comment; update the test around DomainEvents.dispatch(new
TestEvent('No tracking')); await sleep(0); await DomainEvents.allSettled(); to
add a brief comment stating that sleep(0) yields to the event loop so the async
handler can run (since processedCount >= dispatchCount is already true when
tracking is disabled), referencing DomainEvents.dispatch, sleep(0), and
DomainEvents.allSettled to clarify why the no-op delay is required.
packages/domain-events/lib/DomainEvents.js (2)

105-113: Add JSDoc for consistency with other public methods.

These public static methods lack JSDoc documentation, unlike the other methods in this class. For consistency and discoverability, consider adding brief documentation.

📝 Suggested JSDoc
+    /**
+     * Enable or disable dispatch/processed tracking. Test-only.
+     * `@param` {boolean} enabled
+     */
     static setTrackingEnabledForTest(enabled) {
         this.#trackingEnabled = enabled;
     }

+    /**
+     * Clear the await queue and reset dispatch/processed counts. Test-only.
+     */
     static resetTrackingStateForTest() {
         this.#awaitQueue = [];
         this.#dispatchCount = 0;
         this.#processedCount = 0;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/domain-events/lib/DomainEvents.js` around lines 105 - 113, The two
public static test helpers setTrackingEnabledForTest and
resetTrackingStateForTest in DomainEvents.js are missing JSDoc; add brief JSDoc
blocks above each method (mirroring the style used by the other public methods
in this class) describing purpose, parameters (e.g., enabled: boolean for
setTrackingEnabledForTest), side effects (what internal fields like
`#trackingEnabled`, `#awaitQueue`, `#dispatchCount`, `#processedCount` are changed), and
that they are intended for testing only to improve discoverability and
consistency.

41-43: Consider removing empty else branches.

These empty else branches with only comments don't add functional value and create visual noise. The if condition already ensures the guarded code runs only when tracking is enabled. If the intent is documentation, a single comment above or within the if block would suffice without introducing no-op branches.

♻️ Suggested simplification (example for subscribe)
         if (this.#trackingEnabled) {
             this.#onProcessed();
-        } else {
-            // Tracking is disabled outside tests.
         }

Also applies to: 66-68, 100-102

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/domain-events/lib/DomainEvents.js` around lines 41 - 43, Remove the
empty else branches in DomainEvents.js (e.g., the no-op else blocks after the
tracking-enabled checks found around the subscribe and other methods) — delete
the else { // Tracking is disabled ... } blocks and, if documentation is needed,
move a single explanatory comment above the corresponding if
(isTrackingEnabled()) guard or inside the if-block; ensure function names like
subscribe, publish, and any other methods with those empty else branches are
updated accordingly so no no-op branches remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/domain-events/lib/DomainEvents.js`:
- Around line 105-113: The two public static test helpers
setTrackingEnabledForTest and resetTrackingStateForTest in DomainEvents.js are
missing JSDoc; add brief JSDoc blocks above each method (mirroring the style
used by the other public methods in this class) describing purpose, parameters
(e.g., enabled: boolean for setTrackingEnabledForTest), side effects (what
internal fields like `#trackingEnabled`, `#awaitQueue`, `#dispatchCount`,
`#processedCount` are changed), and that they are intended for testing only to
improve discoverability and consistency.
- Around line 41-43: Remove the empty else branches in DomainEvents.js (e.g.,
the no-op else blocks after the tracking-enabled checks found around the
subscribe and other methods) — delete the else { // Tracking is disabled ... }
blocks and, if documentation is needed, move a single explanatory comment above
the corresponding if (isTrackingEnabled()) guard or inside the if-block; ensure
function names like subscribe, publish, and any other methods with those empty
else branches are updated accordingly so no no-op branches remain.

In `@packages/domain-events/test/DomainEvents.test.js`:
- Around line 91-93: The test relies on an async-wrapped handler completing
before calling DomainEvents.allSettled(), but the sleep(0) call is subtle and
deserves an explanatory comment; update the test around
DomainEvents.dispatch(new TestEvent('No tracking')); await sleep(0); await
DomainEvents.allSettled(); to add a brief comment stating that sleep(0) yields
to the event loop so the async handler can run (since processedCount >=
dispatchCount is already true when tracking is disabled), referencing
DomainEvents.dispatch, sleep(0), and DomainEvents.allSettled to clarify why the
no-op delay is required.

In `@packages/job-manager/test/job-manager.test.js`:
- Line 467: The test currently swallows all rejections from
jobManager.removeJob('failed-oneoff') which can hide failures; change the test
to only accept the expected failure instead of catching every error—either
assert the promise rejects with the specific error/message using your test
framework's rejects/toThrow (targeting jobManager.removeJob) or catch and
rethrow unexpected errors by matching the error type/message (e.g., only silence
a known "JobNotFound" or "CannotRemoveRunningJob" error), so the test fails on
real regressions while tolerating the intended failure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3775fa16-0979-4ea2-b87a-4466bebe50e5

📥 Commits

Reviewing files that changed from the base of the PR and between 8e881ad and 7587cd9.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • package.json
  • packages/bookshelf-pagination/test/pagination.test.js
  • packages/config/lib/config.js
  • packages/config/test/config.test.js
  • packages/domain-events/lib/DomainEvents.js
  • packages/domain-events/test/DomainEvents.test.js
  • packages/job-manager/test/job-manager.test.js
  • packages/metrics/test/metrics.test.js
  • packages/request/lib/request.js
  • packages/request/test/request.test.js

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/request/test/request.test.js`:
- Around line 262-264: The test restores process.env.NODE_ENV by assigning
originalNodeEnv, but when originalNodeEnv is undefined that assignment sets the
string "undefined" and pollutes other tests; update both finally blocks that
reference originalNodeEnv so they check if originalNodeEnv === undefined and
call delete process.env.NODE_ENV in that case, otherwise set
process.env.NODE_ENV = originalNodeEnv; reference the variables originalNodeEnv
and process.env.NODE_ENV and the existing finally blocks surrounding the test
promises to locate where to change the restoration logic.
- Around line 250-265: The test title says retry should not be auto-set when
NODE_ENV is unset but it only asserts success; update the test around
request(url, {}) and requestMock so it explicitly verifies no retry metadata was
sent: after deleting process.env.NODE_ENV and before asserting status, assert
that the outgoing request did not include any retry option/header (e.g. use
nock's matchHeader or inspect requestMock.interceptors[0].req.headers to ensure
headers like 'x-retry'/'x-retries' or any retry-related field are absent, or
otherwise confirm no retry option was attached to the request object), then keep
the existing isDone() and statusCode assertions. Ensure you reference
request(url, {}), requestMock and the NODE_ENV deletion in the updated
assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 897d6fd7-32f5-4a33-8d4b-f8d936497d1f

📥 Commits

Reviewing files that changed from the base of the PR and between 16a5df0 and e07053e.

📒 Files selected for processing (3)
  • nx.json
  • package.json
  • packages/request/test/request.test.js
✅ Files skipped from review due to trivial changes (1)
  • nx.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

Comment on lines +250 to +265
it('[success] does not auto-set retry when NODE_ENV is unset', function () {
const url = 'http://some-website.com/unset-env-endpoint/';
const requestMock = nock('http://some-website.com')
.get('/unset-env-endpoint/')
.reply(200, 'ok');
const originalNodeEnv = process.env.NODE_ENV;

delete process.env.NODE_ENV;

return request(url, {}).then((res) => {
assert.equal(requestMock.isDone(), true);
assert.equal(res.statusCode, 200);
}).finally(() => {
process.env.NODE_ENV = originalNodeEnv;
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test title and assertions are misaligned for retry behavior

This test does not verify its claim. On Line 250, the title says retry is not auto-set when NODE_ENV is unset, but assertions at Line 259-262 only validate a successful response. That would pass even if retry were incorrectly injected.

Suggested fix
-    it('[success] does not auto-set retry when NODE_ENV is unset', function () {
-        const url = 'http://some-website.com/unset-env-endpoint/';
-        const requestMock = nock('http://some-website.com')
-            .get('/unset-env-endpoint/')
-            .reply(200, 'ok');
+    it('[success] does not auto-set retry when NODE_ENV is unset', function () {
+        const requestModule = rewire('../lib/request');
+        const gotStub = sinon.stub().resolves({
+            statusCode: 200,
+            body: 'ok'
+        });
         const originalNodeEnv = process.env.NODE_ENV;
 
+        requestModule.__set__('got', gotStub);
+        requestModule.__get__('defaultOptions').dnsLookup = () => {};
         delete process.env.NODE_ENV;
 
-        return request(url, {}).then((res) => {
-            assert.equal(requestMock.isDone(), true);
+        return requestModule('http://some-website.com/unset-env-endpoint/').then((res) => {
             assert.equal(res.statusCode, 200);
+            assert.equal(Object.prototype.hasOwnProperty.call(gotStub.firstCall.args[1], 'retry'), false);
         }).finally(() => {
-            process.env.NODE_ENV = originalNodeEnv;
+            if (originalNodeEnv === undefined) {
+                delete process.env.NODE_ENV;
+            } else {
+                process.env.NODE_ENV = originalNodeEnv;
+            }
         });
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/request/test/request.test.js` around lines 250 - 265, The test title
says retry should not be auto-set when NODE_ENV is unset but it only asserts
success; update the test around request(url, {}) and requestMock so it
explicitly verifies no retry metadata was sent: after deleting
process.env.NODE_ENV and before asserting status, assert that the outgoing
request did not include any retry option/header (e.g. use nock's matchHeader or
inspect requestMock.interceptors[0].req.headers to ensure headers like
'x-retry'/'x-retries' or any retry-related field are absent, or otherwise
confirm no retry option was attached to the request object), then keep the
existing isDone() and statusCode assertions. Ensure you reference request(url,
{}), requestMock and the NODE_ENV deletion in the updated assertions.

Comment on lines +262 to +264
}).finally(() => {
process.env.NODE_ENV = originalNodeEnv;
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Read the test file to see the context around lines 262-264 and 322-324
cat -n packages/request/test/request.test.js | sed -n '250,270p'

Repository: TryGhost/framework

Length of output: 1073


🏁 Script executed:

# Also check the context around line 322-324
cat -n packages/request/test/request.test.js | sed -n '310,330p'

Repository: TryGhost/framework

Length of output: 1043


🏁 Script executed:

# Check how originalNodeEnv is initialized
cat -n packages/request/test/request.test.js | sed -n '1,50p'

Repository: TryGhost/framework

Length of output: 2016


🏁 Script executed:

node <<'EOF'
// Test what happens when assigning undefined to process.env
const originalValue = process.env.TEST_VAR;
delete process.env.TEST_VAR;

// Check if it's deleted
console.log('After delete, TEST_VAR is:', process.env.TEST_VAR);

// Now assign undefined
process.env.TEST_VAR = undefined;

// Check what it becomes
console.log('After assigning undefined, TEST_VAR is:', process.env.TEST_VAR);
console.log('Type:', typeof process.env.TEST_VAR);
console.log('Value === "undefined":', process.env.TEST_VAR === 'undefined');
EOF

Repository: TryGhost/framework

Length of output: 189


🏁 Script executed:

# Check for other similar patterns in the test file where process.env is restored
rg -n "process\.env\.[A-Z_]+ = original" packages/request/test/request.test.js

Repository: TryGhost/framework

Length of output: 173


🏁 Script executed:

# Also check for the delete pattern to see full scope
rg -B5 -A5 "delete process\.env" packages/request/test/request.test.js

Repository: TryGhost/framework

Length of output: 462


Restore NODE_ENV safely to avoid test pollution from string "undefined"

Assigning undefined to process.env.NODE_ENV coerces it to the string "undefined" rather than unsetting the variable. At lines 263 and 323, originalNodeEnv is undefined (captured after delete process.env.NODE_ENV), causing the restoration to set process.env.NODE_ENV = "undefined" instead of deleting it. This leaks state across tests.

Suggested fix
-        }).finally(() => {
-            process.env.NODE_ENV = originalNodeEnv;
-        });
+        }).finally(() => {
+            if (originalNodeEnv === undefined) {
+                delete process.env.NODE_ENV;
+            } else {
+                process.env.NODE_ENV = originalNodeEnv;
+            }
+        });

Also applies to: 322-324

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/request/test/request.test.js` around lines 262 - 264, The test
restores process.env.NODE_ENV by assigning originalNodeEnv, but when
originalNodeEnv is undefined that assignment sets the string "undefined" and
pollutes other tests; update both finally blocks that reference originalNodeEnv
so they check if originalNodeEnv === undefined and call delete
process.env.NODE_ENV in that case, otherwise set process.env.NODE_ENV =
originalNodeEnv; reference the variables originalNodeEnv and
process.env.NODE_ENV and the existing finally blocks surrounding the test
promises to locate where to change the restoration logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant