Skip to content

fix(google-maps): avoid recentering on inline options#747

Merged
harlan-zw merged 1 commit into
mainfrom
fix/google-maps-inline-options-recenter
May 7, 2026
Merged

fix(google-maps): avoid recentering on inline options#747
harlan-zw merged 1 commit into
mainfrom
fix/google-maps-inline-options-recenter

Conversation

@harlan-zw
Copy link
Copy Markdown
Collaborator

Summary

  • avoid reapplying unchanged non-position Google Maps options when inline mapOptions are recreated by unrelated renders
  • watch a stable controlled center key instead of the recomputed options object so toggling child overlays does not call setCenter
  • move the playground SFC zoom into mapOptions to cover the migration path from deprecated top-level zoom
  • add regression coverage for unchanged inline options and stable center watch keys

Fixes #737.

Testing

  • pnpm vitest run test/unit/google-maps-regressions.test.ts --project unit
  • pnpm eslint packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.vue test/unit/google-maps-regressions.test.ts

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
scripts-playground Ready Ready Preview, Comment May 7, 2026 4:14am

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 7, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@nuxt/scripts@747

commit: 24f6638

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the ScriptGoogleMaps component to prevent unintended map centering when equivalent options are re-evaluated. The changes introduce a controlledCenterKey computed property to manage center resolution across override, map options, and top-level props. A new getReactiveMapOptions() function excludes position-related fields (center, zoom, mapId, colorScheme) from option updates, paired with deep equality helpers (isSameOptionValue, isSameMapOptions) to detect structural changes. Map option and zoom watchers were rewritten to apply updates only when meaningful changes are detected, preventing unnecessary setOptions and setCenter calls. A playground example was updated to relocate the zoom prop into mapOptions, and new regression test coverage was added to validate the stable behavior across re-renders.

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 14.29% 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 title clearly and specifically summarizes the main change: preventing map recentering when inline options are recreated.
Description check ✅ Passed The description is well-related to the changeset, detailing the specific issues fixed and testing approach for validating the changes.
Linked Issues check ✅ Passed The PR fully addresses issue #737 by preventing unexpected map recentering when mapOptions.zoom is set and toggling child overlays.
Out of Scope Changes check ✅ Passed All changes are within scope: ScriptGoogleMaps.vue component refactoring, playground SFC zoom migration, and regression tests directly address the linked issue.

✏️ 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 fix/google-maps-inline-options-recenter

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown

@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 (3)
packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue (2)

506-531: 💤 Low value

Two-stage clear → re-center flow depends on watcher ordering — verify under flush semantics.

The Line 506 watcher clears centerOverride and the Line 509 watcher (sourced on controlledCenterKey) is responsible for the resulting setCenter. Both are registered inside onMounted in order, so under default flush: 'pre' they should batch and run in registration order in the same tick, with Line 509 reading the latest controlledCenterKey after the override is cleared.

Worth keeping in mind if either watcher is ever moved to flush: 'post' or 'sync', or split into a separate composable — the ordering invariant becomes load-bearing. A short comment on Line 506 noting “relies on Line 509 watcher firing after this in the same tick to apply the new center” would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue`
around lines 506 - 531, The two related watchers— the one that clears
centerOverride (watch([...], () => { centerOverride.value = undefined })) and
the watcher on controlledCenterKey/isMapReady/map that calls
map.value!.setCenter(...) — rely on execution ordering within the same tick
(default flush: 'pre') so the clear happens before the re-center; add a short
inline comment above the first watcher (the centerOverride clear) stating this
ordering dependency and warning future maintainers that changing watcher flush
mode or moving/splitting these watchers into another composable could break the
two-stage clear → re-center flow, referencing the
controlledCenterKey/isMapReady/map watcher and getCenterWatchKey for clarity.

292-316: 💤 Low value

Class-instance values short-circuit to reference equality.

isSameOptionValue only walks plain objects and arrays; anything else (including google.maps.LatLngBounds, MVCArray, Date, etc.) falls through to Object.is. If a consumer passes such an instance via mapOptions (e.g. restriction.latLngBounds as a LatLngBounds instance) and re-creates it inline on each render, the guard at Line 448 will keep returning false and setOptions will still fire on every unrelated re-render — not the original setCenter bug, but the same class of regression for non-position fields.

This is acceptable given the excluded fields cover the most common cases, but it’s worth either documenting the limitation in the helper’s comment or at least narrowing the guarantee in the JSDoc so users know which option shapes are de-duped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue`
around lines 292 - 316, isSameOptionValue short-circuits to Object.is for
non-plain objects (e.g., google.maps.LatLngBounds, MVCArray, Date), so
recreating class-instance map option values will always appear changed and
trigger setOptions; update the helper's JSDoc/comment on isSameOptionValue
(and/or isSameMapOptions) to explicitly state it only performs deep equality for
plain objects and arrays and does not compare class instances or built‑in types
by structural value, or alternatively extend the function to handle specific
classes if desired (mentioning isPlainObject, isSameOptionValue, and
isSameMapOptions to locate the logic).
test/unit/google-maps-regressions.test.ts (1)

340-371: 💤 Low value

Equality helpers duplicated from the component.

isPlainObject / isSameOptionValue are copy-pasted from ScriptGoogleMaps.vue (Lines 292-312). That’s fine for a unit test of the behavior, but it means the test will keep passing if the component’s implementation drifts (e.g. someone tightens or weakens the deep-eq rules) while the test reimplements the old logic.

Consider either:

  • exporting the helpers from the component (or a sibling module) and importing them here, or
  • replacing the local copy with expect(map.setOptions).not.toHaveBeenCalled() plus an explicit reference-stability test, so the regression spec asserts the externally observable contract instead of re-implementing internals.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit/google-maps-regressions.test.ts` around lines 340 - 371, The test
duplicates deep-equality helpers (isPlainObject, isSameOptionValue,
applyOptionsWithStableGuard) from ScriptGoogleMaps.vue which risks drift; either
export and import the real helpers from the component (move/ export
isPlainObject and isSameOptionValue from ScriptGoogleMaps.vue and import them
into google-maps-regressions.test.ts and use them in
applyOptionsWithStableGuard), or remove the duplicated logic and change the spec
to assert only observable behavior (use
expect(map.setOptions).not.toHaveBeenCalled() for the stability case and add a
reference-stability test that mutates options and verifies setOptions is/ is not
called) so the test no longer re-implements internal equality logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue`:
- Around line 506-531: The two related watchers— the one that clears
centerOverride (watch([...], () => { centerOverride.value = undefined })) and
the watcher on controlledCenterKey/isMapReady/map that calls
map.value!.setCenter(...) — rely on execution ordering within the same tick
(default flush: 'pre') so the clear happens before the re-center; add a short
inline comment above the first watcher (the centerOverride clear) stating this
ordering dependency and warning future maintainers that changing watcher flush
mode or moving/splitting these watchers into another composable could break the
two-stage clear → re-center flow, referencing the
controlledCenterKey/isMapReady/map watcher and getCenterWatchKey for clarity.
- Around line 292-316: isSameOptionValue short-circuits to Object.is for
non-plain objects (e.g., google.maps.LatLngBounds, MVCArray, Date), so
recreating class-instance map option values will always appear changed and
trigger setOptions; update the helper's JSDoc/comment on isSameOptionValue
(and/or isSameMapOptions) to explicitly state it only performs deep equality for
plain objects and arrays and does not compare class instances or built‑in types
by structural value, or alternatively extend the function to handle specific
classes if desired (mentioning isPlainObject, isSameOptionValue, and
isSameMapOptions to locate the logic).

In `@test/unit/google-maps-regressions.test.ts`:
- Around line 340-371: The test duplicates deep-equality helpers (isPlainObject,
isSameOptionValue, applyOptionsWithStableGuard) from ScriptGoogleMaps.vue which
risks drift; either export and import the real helpers from the component (move/
export isPlainObject and isSameOptionValue from ScriptGoogleMaps.vue and import
them into google-maps-regressions.test.ts and use them in
applyOptionsWithStableGuard), or remove the duplicated logic and change the spec
to assert only observable behavior (use
expect(map.setOptions).not.toHaveBeenCalled() for the stability case and add a
reference-stability test that mutates options and verifies setOptions is/ is not
called) so the test no longer re-implements internal equality logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7cf7e131-cf75-4a94-8c69-35576f24a509

📥 Commits

Reviewing files that changed from the base of the PR and between b0ca869 and 24f6638.

📒 Files selected for processing (3)
  • packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue
  • playground/pages/third-parties/google-maps/sfcs.vue
  • test/unit/google-maps-regressions.test.ts

@harlan-zw harlan-zw merged commit 65c3b56 into main May 7, 2026
16 checks passed
@harlan-zw harlan-zw deleted the fix/google-maps-inline-options-recenter branch May 7, 2026 06:37
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.

<ScriptGoogleMaps> gets centered when mapOptions.zoom is set

1 participant