Skip to content

fix: gate analytics scripts on cookie consent#1168

Open
suyua9 wants to merge 4 commits into
f:mainfrom
suyua9:fix/analytics-consent-gate
Open

fix: gate analytics scripts on cookie consent#1168
suyua9 wants to merge 4 commits into
f:mainfrom
suyua9:fix/analytics-consent-gate

Conversation

@suyua9

@suyua9 suyua9 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • do not render Google Analytics scripts until cookie consent is accepted
  • keep rejected or pending consent from loading either the external gtag script or the inline config script
  • add component coverage for pending, rejected, and accepted consent states

Tests

  • git diff --check

Attempted but local dependencies are not installed in this checkout:

  • npm test -- --run src/__tests__/components/analytics.test.tsx (vitest: command not found)
  • npx vitest run src/__tests__/components/analytics.test.tsx (fails to load project config because local vitest/config and @vitejs/plugin-react are not installed)

Summary by CodeRabbit

  • Bug Fixes

    • Analytics now respects user cookie consent and will not load external analytics scripts or initialize tracking unless consent is explicitly accepted.
  • Tests

    • Added tests for consent scenarios (no consent, rejected, accepted) verifying scripts and analytics are only injected and initialized when consent is granted.

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown

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: c0bf5810-6d14-4910-965b-54e8d90314fd

📥 Commits

Reviewing files that changed from the base of the PR and between 7579ca6 and 3ce069d.

📒 Files selected for processing (1)
  • src/__tests__/components/analytics.test.tsx

📝 Walkthrough

Walkthrough

Analytics component now short-circuits rendering when cookie consent is not "accepted". Tests added to assert no scripts render for unset or "rejected" consent and that the GTM script plus a rendered Google Analytics element include the gaId when consent is "accepted".

Changes

Cohort / File(s) Summary
Analytics component
src/components/layout/analytics.tsx
Render now returns null unless localStorage.cookie-consent === "accepted", preventing script injection when consent is not given.
Analytics tests
src/__tests__/components/analytics.test.tsx
New test suite mocking next/script to expose id/src via test ids and data-src; tests cover unset, "rejected", and "accepted" consent flows and assert presence/absence of external script container and google-analytics element (includes gaId).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant Analytics
    participant GTM_Script
    User->>Browser: Load page
    Browser->>Analytics: Mount component
    Analytics->>Browser: Read localStorage.cookie-consent
    alt consent == "accepted"
        Analytics->>GTM_Script: Inject external GTM script (data-src includes gaId)
        GTM_Script-->>Browser: External script loads
        Browser-->>Analytics: Google analytics element rendered with gaId
    else consent != "accepted"
        Analytics-->>Browser: Return null (no script injected)
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I peek at crumbs of consent, then hop,

No script shall enter where choice won't stop.
When humans nod "accepted", I gladly cheer,
Till then I sit quiet, nibbling on a carrot near. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: gate analytics scripts on cookie consent' directly and clearly summarizes the main change: preventing analytics scripts from rendering until cookie consent is accepted.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/__tests__/components/analytics.test.tsx (1)

19-33: Optional: exercise the post-useEffect state in the negative cases too.

For the "no consent" and "rejected" tests, assertions run synchronously right after render, so they pass purely because initial hasConsent is falseuseEffect hasn't had a chance to run yet. If the component is ever refactored to initialize hasConsent differently (e.g., eager read to avoid first-paint flicker), these tests would silently keep passing while no longer testing the post-effect path. Consider wrapping the negative assertions in waitFor so they also observe the state after the effect fires.

♻️ Proposed tweak
-    render(<Analytics gaId="G-TEST" />);
-
-    expect(screen.queryByTestId("external-script")).not.toBeInTheDocument();
-    expect(screen.queryByTestId("google-analytics")).not.toBeInTheDocument();
+    render(<Analytics gaId="G-TEST" />);
+
+    await waitFor(() => {
+      expect(screen.queryByTestId("external-script")).not.toBeInTheDocument();
+      expect(screen.queryByTestId("google-analytics")).not.toBeInTheDocument();
+    });

(and mark both it callbacks async.)

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

In `@src/__tests__/components/analytics.test.tsx` around lines 19 - 33, The tests
for the Analytics component are asserting immediately after render so they only
observe the initial hasConsent state (before useEffect runs); make both negative
test cases async and wrap the assertions in a waitFor to verify the
post-useEffect DOM (i.e., that elements with testIds "external-script" and
"google-analytics" remain absent after the effect runs). Ensure you still set
localStorage where intended (e.g., "cookie-consent" = "rejected") before render,
and reference the Analytics component and its internal hasConsent/useEffect
behavior when updating the tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/__tests__/components/analytics.test.tsx`:
- Around line 19-33: The tests for the Analytics component are asserting
immediately after render so they only observe the initial hasConsent state
(before useEffect runs); make both negative test cases async and wrap the
assertions in a waitFor to verify the post-useEffect DOM (i.e., that elements
with testIds "external-script" and "google-analytics" remain absent after the
effect runs). Ensure you still set localStorage where intended (e.g.,
"cookie-consent" = "rejected") before render, and reference the Analytics
component and its internal hasConsent/useEffect behavior when updating the
tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 33e141d8-2d2f-4e92-9e85-f4151d1f29a7

📥 Commits

Reviewing files that changed from the base of the PR and between c93bf66 and 820a297.

📒 Files selected for processing (2)
  • src/__tests__/components/analytics.test.tsx
  • src/components/layout/analytics.tsx

@suyua9

suyua9 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up pushed in 7579ca60 for the failing Test job. The failure was ESLint flagging the test mock's literal <script> element via @next/next/no-sync-scripts.\n\nThe mock now renders a neutral div with data-src while preserving the assertions that Analytics only injects the script payload after accepted cookie consent.\n\nValidation run locally: git diff --check. Full npm validation is left to the refreshed CI because the local dependency install hit a transient registry/network reset.

@suyua9

suyua9 commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed CodeRabbit's follow-up nitpick on the Analytics regression tests.

What changed:

  • made both negative consent cases async
  • wrapped the absence assertions in waitFor, so the tests now observe the post-useEffect state before confirming no analytics scripts are rendered
  • merged the branch with the latest main; the PR diff remains limited to the Analytics component and its test

Local validation:

  • npm test -- --run src/__tests__/components/analytics.test.tsx (3 tests passed)
  • npx eslint src/__tests__/components/analytics.test.tsx src/components/layout/analytics.tsx (no errors; one existing React hook warning remains for the current consent effect shape)
  • git diff --check origin/main...HEAD

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