Skip to content

feat: make features configurable#246

Open
huang-julien wants to merge 12 commits intomainfrom
feat/feature_configuration
Open

feat: make features configurable#246
huang-julien wants to merge 12 commits intomainfrom
feat/feature_configuration

Conversation

@huang-julien
Copy link
Member

🔗 Linked issue

close #196

📚 Description

Add configuration object for features to disable either logs or devtools integration

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a feature-flag system for runtime hints: new types Features and FeatureFlags, a features field on ModuleOptions, and a build-time hints config exposed via a client plugin as nuxtApp.hints.config.features. Introduces createHintsLogger and per-feature logger exports. Adds four FeatureCards components (WebVitals, Hydration, LazyLoad, ThirdPartyScripts), a feature-gate middleware that gates pages by feature flags, client route constants, and multiple import/path adjustments. Updates pages and the index to render feature-driven cards and includes unit tests for the logger.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat: make features configurable' directly and accurately describes the main change: introducing configurability for features to control logs and devtools.
Description check ✅ Passed The pull request description is related to the changeset, referencing issue #196 and explaining the intent to add configuration for features to disable logs or devtools.
Linked Issues check ✅ Passed The PR implements the core objective from #196: adds configuration to control feature behavior, allowing users to disable console logger output and/or devtools integration per feature. Implements FeatureFlags with 'logs' and 'devtools' boolean properties and feature-gate middleware [#196].
Out of Scope Changes check ✅ Passed All changes are scoped to the feature configuration objective. The PR includes new FeatureCards components, feature-gate middleware, composables, plugins, utilities, tests, and refactored imports—all in support of the configurable features objective with no unrelated changes.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/feature_configuration

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.

Actionable comments posted: 3

🧹 Nitpick comments (6)
client/app/components/FeatureCards/LazyLoadCard.vue (2)

3-8: Consider extracting inline types for better maintainability.

The inline type annotations are verbose and duplicate type definitions that likely exist in the schema. This reduces type safety if the underlying types change.

💡 Suggested improvement

Import types from the schema file instead of inline annotations:

 <script setup lang="ts">
+import type { ComponentLazyLoadData } from '~/runtime/lazy-load/schema'
+
 const lazyLoad = useNuxtApp().$lazyLoadHints
 const lazyLoadCount = computed(() =>
-  lazyLoad.value.reduce(
-    (sum: number, entry: { state: { directImports: { rendered: boolean }[] } }) => sum + entry.state.directImports.filter((i: { rendered: boolean }) => !i.rendered).length,
+  lazyLoad.value.reduce<number>(
+    (sum, entry: ComponentLazyLoadData) => sum + entry.state.directImports.filter(i => !i.rendered).length,
     0,
   ),
 )
 </script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/app/components/FeatureCards/LazyLoadCard.vue` around lines 3 - 8, The
computed lazyLoadCount uses verbose inline types for entry and directImports;
extract and import the corresponding types from your schema (e.g., LazyLoadEntry
and DirectImport or whatever names are used in your types file) and replace the
inline annotations in the lazyLoadCount computed and any related variables/props
so the signature becomes e.g. computed(() => ... ) typed with those imported
types; update the lazyLoad variable/type to use the imported LazyLoadEntry[] and
ensure any .state.directImports elements use the imported DirectImport type to
keep types centralized and maintainable (adjust names to match the schema
exports).

36-38: Consider pluralization for grammar correctness.

Similar to WebVitalsCard, "1 suggestions" should be "1 suggestion".

💡 Suggested fix
-        {{ lazyLoadCount }} suggestions
+        {{ lazyLoadCount }} {{ lazyLoadCount === 1 ? 'suggestion' : 'suggestions' }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/app/components/FeatureCards/LazyLoadCard.vue` around lines 36 - 38,
The displayed text in LazyLoadCard.vue incorrectly uses "suggestions" for all
counts; update the template to pluralize properly by using lazyLoadCount to
choose "suggestion" vs "suggestions" (e.g., inline ternary or a new computed
property like lazyLoadLabel that returns `${lazyLoadCount} suggestion` when
lazyLoadCount === 1 and `${lazyLoadCount} suggestions` otherwise), and replace
the current interpolation that uses "{{ lazyLoadCount }} suggestions" with that
pluralized label; reference lazyLoadCount and the LazyLoadCard.vue template to
locate the change.
client/app/components/FeatureCards/WebVitalsCard.vue (1)

27-31: Consider pluralization for better UX.

When allMetrics.length is 1, the text displays "1 issues" which is grammatically incorrect.

💡 Suggested fix for pluralization
       <n-badge
         v-if="allMetrics.length"
       >
-        {{ allMetrics.length }} issues
+        {{ allMetrics.length }} {{ allMetrics.length === 1 ? 'issue' : 'issues' }}
       </n-badge>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/app/components/FeatureCards/WebVitalsCard.vue` around lines 27 - 31,
The badge shows "{{ allMetrics.length }} issues" which reads incorrectly when
allMetrics.length === 1; update the rendering in WebVitalsCard.vue (the n-badge)
to pluralize based on allMetrics.length (e.g., use a ternary or a
computed/property like issueCountLabel that returns "1 issue" vs `${n} issues`)
so the displayed text is singular for 1 and plural otherwise.
client/app/middleware/feature-gate.ts (1)

1-1: Consider using a path alias for the import.

The relative path ../../../src/runtime/core/types is deep and fragile. If the project has path aliases configured (e.g., #hints or similar), using them would improve maintainability.

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

In `@client/app/middleware/feature-gate.ts` at line 1, The import uses a fragile
relative path ("../../../src/runtime/core/types") to bring in the Features type;
replace it with the project's configured path alias (e.g., "#hints", "@src", or
whatever alias is used) to make the import stable and maintainable — update the
import statement that references Features in
client/app/middleware/feature-gate.ts to use the alias form and ensure your
tsconfig/webpack/next config supports that alias so the Features type resolves
correctly.
src/runtime/core/plugins/features.client.ts (1)

8-16: Getter creates a new frozen object on every access.

The getter returns a new Object.freeze() wrapped object each time nuxtApp.hints is accessed. This creates unnecessary allocations. Consider caching the frozen object.

Suggested fix
 export default defineNuxtPlugin({
     name: 'hints:features',
     setup(nuxtApp) {
+        const hints = Object.freeze({
+            config: {
+                features
+            }
+        })
         Object.defineProperty(nuxtApp, 'hints', {
             get() {
-                return Object.freeze({
-                    config: {
-                        features
-                    }
-                })
+                return hints
             }, 
         })
     }
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/core/plugins/features.client.ts` around lines 8 - 16, The getter
for nuxtApp.hints currently creates a new Object.freeze(...) each access causing
allocations; modify the code around Object.defineProperty on nuxtApp to compute
and store the frozen hints object once (e.g., a local cachedHints variable
closed over by the getter) and have the get() return that cachedHints
(constructed from features) instead of recreating it on every access.
src/module.ts (1)

95-102: Consider defensive handling for partial FeatureFlags objects.

If a user provides an incomplete FeatureFlags object (e.g., { logs: false } without devtools), the current implementation passes it through as-is, potentially leaving properties undefined at runtime. While TypeScript should catch this at compile time, merging with defaults would add runtime safety.

🛡️ Proposed defensive fix
 function booleanToFeatureFlags(input: Record<string, boolean | FeatureFlags>): Record<string, FeatureFlags> {
+  const defaultFlags: FeatureFlags = { logs: true, devtools: true }
   const output: Record<string, FeatureFlags> = {}
   for (const key in input) {
     const value = input[key]
-    output[key] = typeof value === 'object' ? value : { logs: Boolean(value), devtools: Boolean(value) }
+    output[key] = typeof value === 'object' && value !== null
+      ? { ...defaultFlags, ...value }
+      : { logs: Boolean(value), devtools: Boolean(value) }
   }
-  return output 
+  return output
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/module.ts` around lines 95 - 102, The booleanToFeatureFlags function
currently passes object values through as-is, which can leave properties
undefined for partial FeatureFlags; update booleanToFeatureFlags to defensively
merge any object value with explicit defaults (e.g., logs/devtools defaulting to
false) and coerce each flag to a boolean so that output[key] always has both
logs and devtools defined; locate the function by name booleanToFeatureFlags and
ensure you handle object branches by reading potential partials and producing a
fully populated FeatureFlags object for each key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/app/composables/features.ts`:
- Around line 9-12: The function useHintsFeature currently claims to return
FeatureFlags but reads config.features[feature], which can be undefined; update
the implementation to either change the declared return type of useHintsFeature
to FeatureFlags | undefined or validate and return a safe default (e.g., a
fallback FeatureFlags object) before returning; locate the function
useHintsFeature and the config.features lookup to implement the type change or
add a guard that throws or returns a default when the key is missing.

In `@test/unit/logger.test.ts`:
- Around line 38-48: Tests for createHintsLogger rely on process.stdout.write
spy state and can leak calls between cases; add a setup step that clears or
resets the stdout spy before each test so assertions like
expect(process.stdout.write).not.toHaveBeenCalledWith(...) are reliable.
Specifically, in the logger.test.ts suite add a beforeEach that calls
jest.clearAllMocks() or resets the specific spy on process.stdout.write (created
where createHintsLogger is used) so both the "logs messages when enabled" and
"does not log messages when disabled" tests run with a clean spy state.
- Around line 15-16: The test creates a global spy on process.stdout.write
outside test lifecycle which pollutes other tests; move the
vi.spyOn(process.stdout, 'write') call into a beforeEach inside the
describe('createHintsLogger') block and add an afterEach (imported from vitest)
that restores the spy (e.g., call vi.restoreAllMocks() or spy.mockRestore()) so
each test gets a fresh spy and no cross-test leakage occurs; update the imports
to include afterEach and ensure the symbol process.stdout.write is the one being
spied/restored.

---

Nitpick comments:
In `@client/app/components/FeatureCards/LazyLoadCard.vue`:
- Around line 3-8: The computed lazyLoadCount uses verbose inline types for
entry and directImports; extract and import the corresponding types from your
schema (e.g., LazyLoadEntry and DirectImport or whatever names are used in your
types file) and replace the inline annotations in the lazyLoadCount computed and
any related variables/props so the signature becomes e.g. computed(() => ... )
typed with those imported types; update the lazyLoad variable/type to use the
imported LazyLoadEntry[] and ensure any .state.directImports elements use the
imported DirectImport type to keep types centralized and maintainable (adjust
names to match the schema exports).
- Around line 36-38: The displayed text in LazyLoadCard.vue incorrectly uses
"suggestions" for all counts; update the template to pluralize properly by using
lazyLoadCount to choose "suggestion" vs "suggestions" (e.g., inline ternary or a
new computed property like lazyLoadLabel that returns `${lazyLoadCount}
suggestion` when lazyLoadCount === 1 and `${lazyLoadCount} suggestions`
otherwise), and replace the current interpolation that uses "{{ lazyLoadCount }}
suggestions" with that pluralized label; reference lazyLoadCount and the
LazyLoadCard.vue template to locate the change.

In `@client/app/components/FeatureCards/WebVitalsCard.vue`:
- Around line 27-31: The badge shows "{{ allMetrics.length }} issues" which
reads incorrectly when allMetrics.length === 1; update the rendering in
WebVitalsCard.vue (the n-badge) to pluralize based on allMetrics.length (e.g.,
use a ternary or a computed/property like issueCountLabel that returns "1 issue"
vs `${n} issues`) so the displayed text is singular for 1 and plural otherwise.

In `@client/app/middleware/feature-gate.ts`:
- Line 1: The import uses a fragile relative path
("../../../src/runtime/core/types") to bring in the Features type; replace it
with the project's configured path alias (e.g., "#hints", "@src", or whatever
alias is used) to make the import stable and maintainable — update the import
statement that references Features in client/app/middleware/feature-gate.ts to
use the alias form and ensure your tsconfig/webpack/next config supports that
alias so the Features type resolves correctly.

In `@src/module.ts`:
- Around line 95-102: The booleanToFeatureFlags function currently passes object
values through as-is, which can leave properties undefined for partial
FeatureFlags; update booleanToFeatureFlags to defensively merge any object value
with explicit defaults (e.g., logs/devtools defaulting to false) and coerce each
flag to a boolean so that output[key] always has both logs and devtools defined;
locate the function by name booleanToFeatureFlags and ensure you handle object
branches by reading potential partials and producing a fully populated
FeatureFlags object for each key.

In `@src/runtime/core/plugins/features.client.ts`:
- Around line 8-16: The getter for nuxtApp.hints currently creates a new
Object.freeze(...) each access causing allocations; modify the code around
Object.defineProperty on nuxtApp to compute and store the frozen hints object
once (e.g., a local cachedHints variable closed over by the getter) and have the
get() return that cachedHints (constructed from features) instead of recreating
it on every access.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a6b54a and 32cb57a.

📒 Files selected for processing (25)
  • client/app/components/FeatureCards/HydrationCard.vue
  • client/app/components/FeatureCards/LazyLoadCard.vue
  • client/app/components/FeatureCards/ThirdPartyScriptsCard.vue
  • client/app/components/FeatureCards/WebVitalsCard.vue
  • client/app/composables/features.ts
  • client/app/middleware/feature-gate.ts
  • client/app/pages/component-lazy-load.vue
  • client/app/pages/hydration.vue
  • client/app/pages/index.vue
  • client/app/pages/third-party-scripts.vue
  • client/app/pages/web-vitals.vue
  • src/module.ts
  • src/runtime/core/plugins/features.client.ts
  • src/runtime/core/types.ts
  • src/runtime/hydration/composables.ts
  • src/runtime/hydration/utils.ts
  • src/runtime/lazy-load/plugin.client.ts
  • src/runtime/lazy-load/utils.ts
  • src/runtime/logger.ts
  • src/runtime/third-party-scripts/plugin.client.ts
  • src/runtime/third-party-scripts/utils.ts
  • src/runtime/types.d.ts
  • src/runtime/web-vitals/plugin.client.ts
  • src/runtime/web-vitals/utils.ts
  • test/unit/logger.test.ts

Comment on lines 9 to 12
export function useHintsFeature(feature: Features): FeatureFlags {
const config = useHintsConfig()
return config.features[feature]
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Return type may not match runtime value.

useHintsFeature declares a return type of FeatureFlags, but if the feature key doesn't exist in config.features, it would return undefined. Consider updating the return type to FeatureFlags | undefined or adding validation.

Suggested fix
-export function useHintsFeature(feature: Features): FeatureFlags {
+export function useHintsFeature(feature: Features): FeatureFlags | undefined {
     const config = useHintsConfig()
     return config.features[feature]
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function useHintsFeature(feature: Features): FeatureFlags {
const config = useHintsConfig()
return config.features[feature]
}
export function useHintsFeature(feature: Features): FeatureFlags | undefined {
const config = useHintsConfig()
return config.features[feature]
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/app/composables/features.ts` around lines 9 - 12, The function
useHintsFeature currently claims to return FeatureFlags but reads
config.features[feature], which can be undefined; update the implementation to
either change the declared return type of useHintsFeature to FeatureFlags |
undefined or validate and return a safe default (e.g., a fallback FeatureFlags
object) before returning; locate the function useHintsFeature and the
config.features lookup to implement the type change or add a guard that throws
or returns a default when the key is missing.

Comment on lines +15 to +16
vi.spyOn(process.stdout, 'write')
describe('createHintsLogger', () => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Spy should be reset between tests to prevent pollution.

The process.stdout.write spy is created once outside any beforeEach block and is never reset or restored. This can cause test pollution where calls from previous tests affect assertions in subsequent tests.

Suggested fix
-vi.spyOn(process.stdout, 'write')
 describe('createHintsLogger', () => {
+  beforeEach(() => {
+    vi.spyOn(process.stdout, 'write')
+  })
+
+  afterEach(() => {
+    vi.restoreAllMocks()
+  })
+
   it('returns a consola instance tagged with the feature name', () => {

Note: You'll also need to add afterEach to the imports from vitest.

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

In `@test/unit/logger.test.ts` around lines 15 - 16, The test creates a global spy
on process.stdout.write outside test lifecycle which pollutes other tests; move
the vi.spyOn(process.stdout, 'write') call into a beforeEach inside the
describe('createHintsLogger') block and add an afterEach (imported from vitest)
that restores the spy (e.g., call vi.restoreAllMocks() or spy.mockRestore()) so
each test gets a fresh spy and no cross-test leakage occurs; update the imports
to include afterEach and ensure the symbol process.stdout.write is the one being
spied/restored.

Comment on lines 38 to 48
it('logs messages when enabled', () => {
const logger = createHintsLogger('hydration')
logger.info('Test message')
expect(process.stdout.write).toHaveBeenCalledWith(expect.stringContaining('Test message'))
})

it('does not log messages when disabled', () => {
const logger = createHintsLogger('lazyLoad')
logger.info('This should not be logged')
expect(process.stdout.write).not.toHaveBeenCalledWith(expect.stringContaining('This should not be logged'))
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tests may be affected by spy state from previous tests.

The "logging behavior" tests rely on checking process.stdout.write calls, but without spy resets between tests, assertions like not.toHaveBeenCalledWith at line 47 could pass incorrectly if the spy accumulated calls from earlier tests.

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

In `@test/unit/logger.test.ts` around lines 38 - 48, Tests for createHintsLogger
rely on process.stdout.write spy state and can leak calls between cases; add a
setup step that clears or resets the stdout spy before each test so assertions
like expect(process.stdout.write).not.toHaveBeenCalledWith(...) are reliable.
Specifically, in the logger.test.ts suite add a beforeEach that calls
jest.clearAllMocks() or resets the specific spy on process.stdout.write (created
where createHintsLogger is used) so both the "logs messages when enabled" and
"does not log messages when disabled" tests run with a clean spy state.

@huang-julien huang-julien changed the title Feat/feature configuration feat: make features configurable Feb 26, 2026
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 26, 2026

Open in StackBlitz

npm i https://pkg.pr.new/nuxt/hints/@nuxt/hints@246

commit: a9ff7d2

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 (1)
client/app/utils/routes.ts (1)

8-8: Compose HINTS_SSE_ROUTE from the base route to reduce drift risk.

Line 8 is currently a duplicated literal. Composing it from HINTS_ROUTE keeps all child routes consistent if the base path changes later.

♻️ Proposed refactor
 export const HINTS_ROUTE = '/__nuxt_hints'
-export const HINTS_SSE_ROUTE = '/__nuxt_hints/sse'
+export const HINTS_SSE_ROUTE = `${HINTS_ROUTE}/sse`
 export const HYDRATION_ROUTE = `${HINTS_ROUTE}/hydration`
 export const LAZY_LOAD_ROUTE = `${HINTS_ROUTE}/lazy-load`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/app/utils/routes.ts` at line 8, Replace the duplicated literal
HINTS_SSE_ROUTE with a value derived from the base HINTS_ROUTE to avoid drift:
in routes.ts change the export for HINTS_SSE_ROUTE so it composes from
HINTS_ROUTE (e.g., use string concatenation or a template `${HINTS_ROUTE}/sse`)
instead of the hard-coded '/__nuxt_hints/sse', keeping any leading/trailing
slashes consistent with HINTS_ROUTE.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@client/app/utils/routes.ts`:
- Line 8: Replace the duplicated literal HINTS_SSE_ROUTE with a value derived
from the base HINTS_ROUTE to avoid drift: in routes.ts change the export for
HINTS_SSE_ROUTE so it composes from HINTS_ROUTE (e.g., use string concatenation
or a template `${HINTS_ROUTE}/sse`) instead of the hard-coded
'/__nuxt_hints/sse', keeping any leading/trailing slashes consistent with
HINTS_ROUTE.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32cb57a and b0055e4.

📒 Files selected for processing (6)
  • client/app/components/HydrationIssue.vue
  • client/app/pages/component-lazy-load.vue
  • client/app/plugins/0.sse.ts
  • client/app/plugins/hydration.ts
  • client/app/plugins/lazy-load.ts
  • client/app/utils/routes.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/app/pages/component-lazy-load.vue

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.

♻️ Duplicate comments (2)
test/unit/logger.test.ts (2)

15-16: ⚠️ Potential issue | 🟡 Minor

Reset process.stdout.write spy per test case.

The spy is installed once and never restored, which can leak calls across tests (especially in the logging behavior block).

🔧 Proposed fix
-import { describe, it, expect, vi } from 'vitest'
+import { afterEach, beforeEach, describe, it, expect, vi } from 'vitest'
@@
-vi.spyOn(process.stdout, 'write')
 describe('createHintsLogger', () => {
+  beforeEach(() => {
+    vi.spyOn(process.stdout, 'write')
+  })
+
+  afterEach(() => {
+    vi.restoreAllMocks()
+  })
+
   it('returns a consola instance tagged with the feature name', () => {
#!/bin/bash
# Verify spy lifecycle hooks exist and the stdout spy is managed per-test.
rg -n "spyOn\\(process.stdout, 'write'\\)|beforeEach\\(|afterEach\\(|restoreAllMocks\\(" test/unit/logger.test.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/logger.test.ts` around lines 15 - 16, The test installs a
persistent spy via vi.spyOn(process.stdout, 'write') which is never restored and
can leak between tests (e.g., the "logging behavior" block); move the spy
creation into a per-test lifecycle (create the spy in a beforeEach and restore
it in an afterEach) or call vi.restoreAllMocks()/spy.mockRestore() in afterEach
so the process.stdout.write spy is reset between tests inside the
describe('createHintsLogger') suite.

11-13: ⚠️ Potential issue | 🔴 Critical

Mock specifier does not match the imported virtual module.

src/runtime/logger.ts imports #hints-config (line 4), but this test mocks #build/hints.config.mjs. The virtual module alias is defined in src/module.ts (lines 40-45) as #hints-config, so the mock never intercepts and the import fails.

Additionally, the spy created on line 15 is never cleaned up, causing test pollution across the suite.

🔧 Proposed fix
-vi.mock('#build/hints.config.mjs', () => ({
+vi.mock('#hints-config', () => ({
   features: mockFeatures,
 }))

Also add after the describe block:

describe('createHintsLogger', () => {
+  afterEach(() => {
+    vi.restoreAllMocks()
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/logger.test.ts` around lines 11 - 13, The mock target is wrong and
the test leaves a spy active: replace vi.mock('#build/hints.config.mjs', ...)
with vi.mock('#hints-config', () => ({ features: mockFeatures })) so it matches
the virtual module imported by src/runtime/logger.ts, and add cleanup for the
spy after the test suite (e.g. add an afterEach or afterAll that calls
vi.restoreAllMocks() or explicitly spy.mockRestore()) to ensure the spy created
on line 15 is cleaned up and doesn't pollute other tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@test/unit/logger.test.ts`:
- Around line 15-16: The test installs a persistent spy via
vi.spyOn(process.stdout, 'write') which is never restored and can leak between
tests (e.g., the "logging behavior" block); move the spy creation into a
per-test lifecycle (create the spy in a beforeEach and restore it in an
afterEach) or call vi.restoreAllMocks()/spy.mockRestore() in afterEach so the
process.stdout.write spy is reset between tests inside the
describe('createHintsLogger') suite.
- Around line 11-13: The mock target is wrong and the test leaves a spy active:
replace vi.mock('#build/hints.config.mjs', ...) with vi.mock('#hints-config', ()
=> ({ features: mockFeatures })) so it matches the virtual module imported by
src/runtime/logger.ts, and add cleanup for the spy after the test suite (e.g.
add an afterEach or afterAll that calls vi.restoreAllMocks() or explicitly
spy.mockRestore()) to ensure the spy created on line 15 is cleaned up and
doesn't pollute other tests.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0055e4 and 2b533b0.

📒 Files selected for processing (5)
  • client/app/composables/features.ts
  • src/module.ts
  • src/runtime/core/plugins/features.client.ts
  • src/runtime/logger.ts
  • test/unit/logger.test.ts

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 (1)
pnpm-workspace.yaml (1)

6-7: The patch serves a legitimate purpose but consider documenting it.

This patch enables the dev option to be configurable via options.overrides, which is a necessary feature for this project. While the patch is currently functional, consider tightening the version constraint on @nuxt/test-utils from ^4.0.0 to a more specific version to reduce the risk of silent patch failures if the package's compiled output changes in future updates.

Adding a brief comment in the PR description explaining why this patch is needed would also improve maintainability.

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

In `@pnpm-workspace.yaml` around lines 6 - 7, The patchedDependencies entry for
'@nuxt/test-utils' points to patches/@nuxt__test-utils.patch but the package
version is too loose; update the version constraint for '@nuxt/test-utils'
(where it is declared in your manifest/package.json or workspace config) from
^4.0.0 to a tighter pin (e.g., exact 4.0.0 or ~4.0.x) to avoid silent patch
breakage, keep the patchedDependencies entry intact, and add a short note in the
PR description explaining why the patch (patches/@nuxt__test-utils.patch) is
required and that it enables the dev option via options.overrides for future
maintainers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pnpm-workspace.yaml`:
- Around line 6-7: The patchedDependencies entry for '@nuxt/test-utils' points
to patches/@nuxt__test-utils.patch but the package version is too loose; update
the version constraint for '@nuxt/test-utils' (where it is declared in your
manifest/package.json or workspace config) from ^4.0.0 to a tighter pin (e.g.,
exact 4.0.0 or ~4.0.x) to avoid silent patch breakage, keep the
patchedDependencies entry intact, and add a short note in the PR description
explaining why the patch (patches/@nuxt__test-utils.patch) is required and that
it enables the dev option via options.overrides for future maintainers.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c3da03 and 56dcc3c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • patches/@nuxt__test-utils.patch
  • pnpm-workspace.yaml
  • vitest.runtime.config.ts

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.

Configuration object

1 participant