From f519483ce4a0c1fb4bf7bfbb7333eb3ade1473bf Mon Sep 17 00:00:00 2001 From: gjulivan Date: Fri, 28 Aug 2026 14:57:19 +0200 Subject: [PATCH 1/2] fix: console warning shown on maps --- .../pluggableWidgets/maps-web/CHANGELOG.md | 4 + .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../tasks.md | 0 .../.openspec.yaml | 2 + .../design.md | 182 ++++++++++++++ .../proposal.md | 90 +++++++ .../tasks.md | 52 ++++ .../pluggableWidgets/maps-web/package.json | 3 +- .../components/__tests__/GoogleMap.spec.tsx | 229 ++++++++++++++++-- .../__snapshots__/GoogleMap.spec.tsx.snap | 36 ++- .../pluggableWidgets/maps-web/tsconfig.json | 2 +- pnpm-lock.yaml | 34 ++- 14 files changed, 597 insertions(+), 37 deletions(-) rename packages/pluggableWidgets/maps-web/openspec/changes/{maps-defer-render-until-key => archive/2026-08-28-maps-defer-render-until-key}/.openspec.yaml (100%) rename packages/pluggableWidgets/maps-web/openspec/changes/{maps-defer-render-until-key => archive/2026-08-28-maps-defer-render-until-key}/design.md (100%) rename packages/pluggableWidgets/maps-web/openspec/changes/{maps-defer-render-until-key => archive/2026-08-28-maps-defer-render-until-key}/proposal.md (100%) rename packages/pluggableWidgets/maps-web/openspec/changes/{maps-defer-render-until-key => archive/2026-08-28-maps-defer-render-until-key}/tasks.md (100%) create mode 100644 packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/.openspec.yaml create mode 100644 packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/design.md create mode 100644 packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/proposal.md create mode 100644 packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/tasks.md diff --git a/packages/pluggableWidgets/maps-web/CHANGELOG.md b/packages/pluggableWidgets/maps-web/CHANGELOG.md index 2207f4b082..b67caf44a7 100644 --- a/packages/pluggableWidgets/maps-web/CHANGELOG.md +++ b/packages/pluggableWidgets/maps-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed + +- We fixed an issue where the browser console showed a deprecation warning for every default marker on a Google Maps map. + ### Changed - We replaced the react-leaflet dependency with a direct Leaflet integration due to licensing considerations. diff --git a/packages/pluggableWidgets/maps-web/openspec/changes/maps-defer-render-until-key/.openspec.yaml b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-maps-defer-render-until-key/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/maps-web/openspec/changes/maps-defer-render-until-key/.openspec.yaml rename to packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-maps-defer-render-until-key/.openspec.yaml diff --git a/packages/pluggableWidgets/maps-web/openspec/changes/maps-defer-render-until-key/design.md b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-maps-defer-render-until-key/design.md similarity index 100% rename from packages/pluggableWidgets/maps-web/openspec/changes/maps-defer-render-until-key/design.md rename to packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-maps-defer-render-until-key/design.md diff --git a/packages/pluggableWidgets/maps-web/openspec/changes/maps-defer-render-until-key/proposal.md b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-maps-defer-render-until-key/proposal.md similarity index 100% rename from packages/pluggableWidgets/maps-web/openspec/changes/maps-defer-render-until-key/proposal.md rename to packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-maps-defer-render-until-key/proposal.md diff --git a/packages/pluggableWidgets/maps-web/openspec/changes/maps-defer-render-until-key/tasks.md b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-maps-defer-render-until-key/tasks.md similarity index 100% rename from packages/pluggableWidgets/maps-web/openspec/changes/maps-defer-render-until-key/tasks.md rename to packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-maps-defer-render-until-key/tasks.md diff --git a/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/.openspec.yaml b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/.openspec.yaml new file mode 100644 index 0000000000..b3f00443d7 --- /dev/null +++ b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/.openspec.yaml @@ -0,0 +1,2 @@ +schema: tdd-refactor +created: 2026-08-28 diff --git a/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/design.md b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/design.md new file mode 100644 index 0000000000..c734c7a25a --- /dev/null +++ b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/design.md @@ -0,0 +1,182 @@ +## Test Cases + +All tests live in `src/components/__tests__/GoogleMap.spec.tsx` unless stated otherwise. + +The `gmp-pin` deprecation warning itself originates from Google's remote Maps JavaScript API script and therefore cannot be observed in jsdom. The tests below assert the _structural_ consequence of the deprecated call instead, which is deterministic under `@googlemaps/jest-mocks` and flips from red to green on the bump. See Notes for why this works. + +### Reproduction Tests + +- renders a `gmp-pin` element for a marker with no custom image (unit) + - **Given**: `initialize()` from `@googlemaps/jest-mocks` has run, and `locations` contains a single marker with `title`, `latitude`, `longitude` and **no** `url` + - **When**: `GoogleMapContainer` renders and pending promises are flushed inside `act` + - **Then**: exactly one `PinElement` instance has been constructed (`mockInstances.get(PinElement)` has length 1), and that instance is attached to the document (`isConnected === true`) + - **Red on `0.8.3`**: `Pin` executes `advancedMarker.content = pinElement.element`. The mocked `PinElement` extends `HTMLElement` and exposes no `element` property, so `content` is assigned `undefined` and the pin never enters the DOM — the `isConnected` assertion fails. + - **Green on `1.9.0`**: `Pin` detects the registered `gmp-pin` custom element, routes to `PinModern`, and appends the `PinElement` instance directly. + +- does not read the deprecated `element` property of `PinElement` (unit) + - **Given**: a getter spy installed on `PinElement.prototype.element` (via `Object.defineProperty`) that records access, restored in `afterEach` + - **When**: a marker with no `url` renders + - **Then**: the getter is never invoked + - **Red on `0.8.3`**: getter invoked once per default marker. + - This test encodes the deprecation directly and is the one that will fail again if the dependency ever regresses. + +### Edge Cases + +- renders an `img` and no pin for a marker with a custom image (unit) + - **Given**: `locations` contains a single marker with `url: "image:url"` + - **When**: the map renders + - **Then**: an `img` with that `src` is present, and **no** `PinElement` was constructed + - Guards the `marker.url && ...` / `!marker.url && ` split in `src/components/GoogleMap.tsx` so a future change cannot silently route image markers through `Pin`. + +- renders a pin for the default marker and an image for the custom marker in one map (unit) + - **Given**: `locations` contains two markers — one with `url`, one without + - **When**: the map renders + - **Then**: exactly one `PinElement` is constructed and exactly one `img` is rendered + +- renders a pin for the current-location marker when it has no image (unit) + - **Given**: `showCurrentLocation: true` and `currentLocation` set with no `url` + - **When**: the map renders + - **Then**: a `PinElement` is constructed for it + - The existing `currentLocation` fixture sets `url`, so this branch is currently unexercised. + +- opens an info window with the marker title when a default pin is clicked (unit) + - **Given**: a marker with no `url` and a `title` + - **When**: the marker's `onClick` fires + - **Then**: the title is rendered in an `InfoWindow` + - `InfoWindowProps.anchor` accepts `AdvancedMarkerElement` in `1.9.0`, so this should hold — worth pinning down because the pin path changes what `content` the anchor wraps. + +### Regression Tests + +- existing six `asFragment()` snapshots still describe the rendered map (unit, existing) + - **Given**: the four dimension-unit cases, the two-marker case and the current-location case already in `GoogleMap.spec.tsx` + - **When**: the suite runs against `1.9.0` + - **Then**: snapshots match, or differ only in ways explained by the 1.x `Map` DOM structure + - Each snapshot diff must be read individually. `pnpm run test -u` is acceptable only after the diff has been reviewed and the change attributed to the library. + +- marker `onClick` still fires for image markers (unit) + - **Given**: a marker with `url` and an `onClick` handler + - **When**: the marker is clicked + - **Then**: the handler is called once + - Covers `AdvancedMarkerEventProps` surviving the major bump. + +- map camera still fits bounds to all markers (unit) + - **Given**: two markers at different coordinates and `autoZoom: true` + - **When**: the map renders + - **Then**: `fitBounds` is called on the map instance; with `autoZoom: false`, `setCenter` is called instead + - Covers the imperative `useMap()` block in `src/components/GoogleMap.tsx:57-78`, which is the part most exposed to the 1.x controlled/uncontrolled camera rework. + +- full widget suite passes unchanged (unit, existing) + - **Given**: the 90 tests across 12 suites currently green + - **When**: `pnpm run test` runs after the bump + - **Then**: all pass; Leaflet, model-layer and util suites are untouched by this change + +- `tsc --noEmit` reports no errors (type check) + - **Given**: `@types/google.maps` moves from `^3.54.10` to `^3.64.0` + - **When**: the type check runs + - **Then**: clean — `GoogleMap.tsx` only uses `LatLngLiteral` and `LatLngBounds` + +## Notes + +**Why the reproduction is deterministic in jsdom.** `@googlemaps/jest-mocks@2.22.8` already models the Maps API 3.62+ world: + +- `PinElement extends HTMLElement` and is registered with `customElements.define("gmp-pin", PinElement)`, so `1.9.0`'s capability check `customElements.get("gmp-pin") !== undefined` returns true and the `PinModern` branch runs under test. +- `importLibrary` is a `jest.fn` whose `"marker"` case returns `{ PinElement, AdvancedMarkerElement, ... }`, so `useMapsLibrary("marker")` resolves. Without this, `1.9.0`'s `Pin` returns `null` and every pin assertion would pass vacuously. +- The mocked `PinElement` has no `element` property, which is exactly what makes the `0.8.3` behaviour observably broken rather than merely deprecated. + +**Guard against vacuous passes.** `1.9.0`'s `Pin` returns `null` until the marker library resolves. Any pin test must therefore flush promises inside `act` and assert on a _positive_ signal (instance constructed and connected), never on absence of an error. A pin test that passes while `mockInstances.get(PinElement)` is empty is not testing anything. + +**Where to assert.** Prefer `mockInstances` from `@googlemaps/jest-mocks` over DOM queries. `AdvancedMarkerElement` is itself a mocked custom element whose `content` is assigned as a property, so pin markup may not surface in `asFragment()` output at all. If it does not appear in snapshots, that is expected and not a defect — assert via the instance registry. + +**Ordering.** Write the two reproduction tests first and confirm they fail on `0.8.3` before changing `package.json`. Skipping the red step forfeits the only automated proof that the bump fixes anything. + +**Out of scope.** Pinning `APIProvider version`, replacing `` with hand-rolled SVG markers, and sweeping the Google path for other 3.62 deprecations were all considered and rejected for this change. + +### Deviation: `isConnected` replaced by attached-pin count (task 1.4) + +The reproduction test above specified `isConnected === true` and `mockInstances.get(PinElement)` having length 1. Running the red step showed both to be wrong: + +``` +● attaches a pin for a marker without a custom image + Expected length: 1 + Received length: 2 + Received array: [, ] +``` + +vis.gl's `Pin` effect lists the props object in its dependency array, and that object has a fresh identity on every render. One default marker therefore constructs one `PinElement` **per render**, so an exact instance count asserts render count rather than behaviour. + +`isConnected` is also wrong: the mocked `AdvancedMarkerElement` is never attached to the rendered document — vis.gl assigns its `content` as a detached DOM subtree — so `isConnected` is `false` even on `1.9.0`. + +The assertion is now "exactly one pin is _attached_ to marker content", via `attachedPins()` filtering on `parentElement !== null`. This is render-count independent because `PinModern` clears existing marker content before appending, so only the newest pin stays attached. Confirmed red on `0.8.3`: + +``` +● attaches a pin for a marker without a custom image + Expected length: 1 + Received length: 0 + Received array: [] + +● does not read the deprecated element property of PinElement + Expected number of calls: 0 + Received number of calls: 2 +``` + +The two `.element` reads correspond to the two constructed instances, confirming one deprecated access per pin construction. + +### Deviation: marker clicks must be version-agnostic (tasks 1.6, 1.7) + +The click mechanism is not stable across the bump, which the design did not anticipate: + +| | click wiring | +| ------- | -------------------------------------------------------------------------------------------------------- | +| `0.8.3` | `google.maps.event.addListener(marker, "click", onClick)` (`dist/index.modern.mjs:907`) | +| `1.9.0` | `useDomEventListener(marker, "gmp-click", onClick)`, i.e. native `addEventListener` (`4:2217`, `4:2013`) | + +A click test written against either mechanism alone passes on one version and fails on the other, which would destroy the regression signal for tasks 1.6 and 1.7 — the test and the library would change together, proving nothing. + +The spec therefore has a `clickMarker()` helper that fires **both**: it invokes the most recently registered `google.maps.event.addListener(marker, "click")` handler _and_ dispatches a native `gmp-click` event. Only one mechanism is ever registered by a given version, so the other path is a no-op. The "most recent handler" detail matters because `AdvancedMarker`'s event effect lists `onClick` in its dependency array and `GoogleMapsMarker` passes an inline arrow, so the listener is re-registered on every render; earlier handlers were already removed by `clearInstanceListeners`. + +Once on `1.9.0`, the `google.maps.event` half of the helper is dead code and should be dropped in the refactor phase (folded into task 3.1). + +### Deviation: marker markup is asserted through `marker.content`, not the document + +`AdvancedMarker` portals its children into a `div` it assigns to `marker.content`, which is never attached to the rendered document. Confirmed by the existing six snapshots: none contains `img` markup even though every existing fixture is an image marker. DOM queries (`getByRole("img")`) and `asFragment()` therefore cannot see marker content at all. + +Image assertions go through `markerImages()`, which walks `marker.content` on markers still attached to the map (`marker.map` truthy, since vis.gl nulls it on cleanup). Info window assertions read the `textContent` of the container passed to `InfoWindow.setContent` — both `0.8.3` and `1.9.0` construct `google.maps.InfoWindow` and call `setContent` with a portal container, so this is version-stable. The container's text is read at assertion time rather than at call time, because `setContent` receives the element while it is still empty. + +### Red/green split confirmed on `0.8.3` + +Of the eleven tests now in the spec, exactly the four pin-dependent ones fail, and they are the four the bump is meant to fix: + +``` +Tests: 4 failed, 11 passed +● default pin markers › attaches a pin for a marker without a custom image +● default pin markers › does not read the deprecated element property of PinElement +● default pin markers › attaches a pin for the current location marker when it has no image +● custom image markers › renders a pin for the default marker and an image for the custom marker +``` + +The image-marker, info-window, `onClick` and camera tests pass on `0.8.3` and must stay green after the bump — that is their entire purpose. Notably `fitBounds` / `setCenter` are mutually exclusive on `0.8.3` (`Map` does not call `setCenter` internally when `autoZoom` is on), so the negative assertion is safe to keep and will catch the 1.x camera rework if it changes that. + +### Deviation: `google.maps.Settings` must be stubbed in the spec + +`1.9.0`'s `APIProvider` calls `google.maps.Settings.getInstance()` once the API reports loaded (`src/components/api-provider.tsx:389`), in order to attach `fetchAppCheckToken`. `@googlemaps/jest-mocks@2.22.8` never creates `google.maps.Settings` — `initialize()` only preserves one if it already exists — so every render threw `TypeError: Cannot read properties of undefined (reading 'getInstance')`. + +Stubbed in `GoogleMap.spec.tsx`'s `beforeEach` with a `getInstance` returning an empty object. This is a gap in the mock library, not a widget defect, and the widget never passes `fetchAppCheckToken`. `MapsWidget.spec.tsx` needs no stub: it never calls `initialize()`, so the API never reports loaded and the effect never runs. + +### Deviation: the bump breaks the build, and the fix is not in `src/` + +The proposal expected no changes beyond `package.json`, the lockfile and the spec. That was wrong. After the bump, `pnpm run build` fails: + +``` +src/components/GoogleMap.tsx (37:27): @rollup/plugin-typescript TS2503: Cannot find namespace 'google'. +``` + +Verified this is caused by the bump and not pre-existing: reverting to `^0.8.3`, reinstalling and rebuilding succeeds, then restoring `^1.9.0` reproduces the failure. + +Cause is a packaging change, not an API change. `0.8.3` shipped unbundled declarations — dozens of `.d.ts` files, one of which carried `/// `. That reference leaked the `google` global namespace into the widget's own compilation, which is how `GoogleMap.tsx` got away with using `google.maps.LatLngLiteral` and `google.maps.LatLngBounds` while `tsconfig.json` restricted `types` to `["jest", "node"]`. `1.9.0` ships a single bundled `dist/index.d.ts` with no such reference, so the global vanished. + +The widget was therefore relying on an accidental transitive type reference. Fixed by declaring the dependency it always had: + +- `package.json` — add `@types/google.maps` `^3.64.0` to `devDependencies` (the version `1.9.0` itself depends on) +- `tsconfig.json` — add `"google.maps"` to `compilerOptions.types` + +`src/` is untouched, so the task 2.2 guardrail ("if production source edits are required, STOP") is not tripped. This also fixes four pre-existing `tsc --noEmit` errors — the same missing namespace in `GoogleMap.tsx:37`, `GoogleMap.tsx:59` and the new spec — which failed before this change because bare `tsc` never saw the transitive reference that Rollup's build did. diff --git a/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/proposal.md b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/proposal.md new file mode 100644 index 0000000000..fa69654173 --- /dev/null +++ b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/proposal.md @@ -0,0 +1,90 @@ +## Why + +The browser console logs a deprecation warning for every default (non-image) marker rendered on a Google Maps map: + +``` +: The `element` property is deprecated. Please use the PinElement directly. +``` + +Observed: one warning per default pin, in any app using the Maps widget with `mapProvider = "googleMaps"` and at least one marker whose `markerStyle` is `default`. + +Expected: no deprecation warnings. Markers still render correctly today, so this is console noise rather than a functional defect — but the deprecated shim will eventually be removed by Google, at which point default pins would break. + +A second, related problem: the code path that produces the warning is never exercised by the test suite, so neither the bug nor its fix can be verified automatically. + +## Root Cause + +Two independent causes compound. + +**1. Outdated `@vis.gl/react-google-maps`.** + +The widget is on `@vis.gl/react-google-maps@0.8.3`. Its `Pin` component unconditionally reads the deprecated `.element` getter: + +```js +// node_modules/@vis.gl/react-google-maps/dist/index.modern.mjs:1231 +advancedMarker.content = pinElement.element; +``` + +Google Maps JavaScript API 3.62 turned `PinElement` into a real custom element registered as ``. Previously `PinElement` was a wrapper object holding its DOM node at `.element`; now the instance _is_ the node, so `.element` returns itself and logs a deprecation warning. + +`src/components/GoogleMap.tsx` renders `` for every marker without a custom image, so the warning fires once per default pin. + +The warning appeared without any change to this repository: `GoogleMap.tsx` passes no `version` to `APIProvider`, so Google serves the `weekly` release channel and rolled the widget forward to 3.62 on its own schedule. + +The library caret range `^0.8.3` is on a `0.x` version, which pnpm resolves to `0.8.x` only. That is why the widget never picked up the upstream fix. + +Upstream resolved this in later releases. As of `1.9.0`, `Pin` feature-detects the custom element and forks: + +- `customElements.get("gmp-pin") !== undefined` → `PinModern`, which appends the `PinElement` directly +- otherwise → `PinLegacy`, which keeps the old `.element` behaviour + +**2. Missing test coverage on the `` branch.** + +`src/components/GoogleMap.tsx` chooses between two marker visuals: + +```tsx +{ + marker.url && ; +} +{ + !marker.url && ; +} +``` + +Every marker fixture in `src/components/__tests__/GoogleMap.spec.tsx` sets `url: "image:url"`, including the `currentLocation` fixture. `` is therefore never rendered in any test, and the six existing snapshots contain no pin markup. + +## What Changes + +- `package.json` — bump `@vis.gl/react-google-maps` from `^0.8.3` to `^1.9.0`, and add `@types/google.maps` `^3.64.0` to `devDependencies` +- `tsconfig.json` — add `"google.maps"` to `compilerOptions.types` +- `pnpm-lock.yaml` — regenerated by pnpm as a result of the bump (lockfile is generated, not hand-edited) +- `src/components/__tests__/GoogleMap.spec.tsx` — add coverage for the default-pin branch: at least one marker fixture with no `url`, asserting that pin content is rendered and that no `gmp-pin` deprecation warning is emitted +- `src/components/__tests__/__snapshots__/GoogleMap.spec.tsx.snap` — updated for the new pin test, plus any DOM changes the 1.x `Map` component introduces to the existing six snapshots + +No changes under `src/` are expected — the `tsconfig.json` and `@types/google.maps` additions above are build configuration, made necessary because `1.9.0` bundles its type declarations and no longer leaks the `google` global namespace into consumers (see design.md). Every vis.gl symbol the widget imports (`APIProvider`, `Map`, `AdvancedMarker`, `InfoWindow`, `Pin`, `MapProps`, `useMap`, `useApiIsLoaded`, `useAdvancedMarkerRef`) still exists in `1.9.0` with a compatible shape, and every `MapProps` option currently passed remains valid. If `GoogleMap.tsx` does need edits, that is a signal to re-scope rather than to patch around. + +Not breaking for Mendix app developers: no widget properties change, no XML changes, no visual change intended. + +## Impact + +**Widget behaviour** — none intended. Default pins keep rendering as Google's default pin; custom-image markers are untouched because they never enter the `` path. + +**Dependencies** — the bump changes the transitive footprint, which affects MPK size and script-loading timing: + +| | `0.8.3` | `1.9.0` | +| -------------------- | ------------------------ | ---------------------------------------- | +| `@types/google.maps` | `^3.54.10` | `^3.64.0` | +| deep equality | `fast-deep-equal@^3.1.3` | `fast-equals@^6.0.0` | +| API loader | inline | `@googlemaps/js-api-loader@^2.0.2` (new) | +| React peer | `>=16.8.0` | `>=16.8.0 \|\| ^19` | + +React `18.3.1` satisfies the new peer range, and the root `pnpm.peerDependencyRules` pin of React to `>=18.0.0 <19.0.0` is unaffected. + +**Blast radius** — `@mendix/maps-web` is the only consumer of `@vis.gl/react-google-maps` in the monorepo, and no root `pnpm.overrides` entry pins it. The bump cannot affect another package. + +**Risks to watch during implementation** + +- Snapshot churn. The 1.x `Map` component changed its internal DOM, so the six existing `asFragment()` snapshots will likely need regenerating. Each diff must be read rather than blindly accepted with `-u`. +- New script loader. 1.x delegates loading to `@googlemaps/js-api-loader` instead of injecting the script itself. The existing API-key render gate in `src/components/MapsWidget.tsx` already prevents map initialisation before the key resolves, so the risk is low, but two Maps widgets on one page share one `APIProvider` script and deserve a manual check. +- Vacuous pin test. In `1.9.0`, `Pin` returns `null` until `useMapsLibrary("marker")` resolves. Under `@googlemaps/jest-mocks` that may never resolve, so a naive test would pass while rendering nothing. The new test must assert on rendered pin content, not merely that rendering did not throw. +- `@types/google.maps` moves ten minor versions. `GoogleMap.tsx` only uses `LatLngLiteral` and `LatLngBounds`, so type fallout should be small, but `tsc --noEmit` must be clean. diff --git a/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/tasks.md b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/tasks.md new file mode 100644 index 0000000000..6fc7a91660 --- /dev/null +++ b/packages/pluggableWidgets/maps-web/openspec/changes/archive/2026-08-28-pin-coverage-visgl-bump/tasks.md @@ -0,0 +1,52 @@ +## 1. Test Setup + + + +- [x] 1.1 Add a `renderGoogleMap` marker fixture with no `url`, plus a helper that flushes pending promises inside `act` so `useMapsLibrary("marker")` resolves +- [x] 1.2 Write failing test: default marker (no `url`) constructs exactly one `PinElement` and that instance is `isConnected` +- [x] 1.3 Write failing test: `PinElement.prototype.element` getter is never accessed (install getter spy via `Object.defineProperty`, restore in `afterEach`) +- [x] 1.4 Confirm 1.2 and 1.3 both FAIL on the current `@vis.gl/react-google-maps@0.8.3` — record the failure output in design.md Notes +- [x] 1.5 Add edge case tests: custom-image marker renders `img` and constructs no `PinElement`; mixed list renders one pin and one `img`; current-location marker without `url` gets a pin +- [x] 1.6 Add edge case test: clicking a default pin opens an `InfoWindow` with the marker title +- [x] 1.7 Add regression tests: marker `onClick` fires for image markers; `fitBounds` called when `autoZoom` is true and `setCenter` when false + +## 2. Implementation + + + +- [x] 2.1 Bump `@vis.gl/react-google-maps` from `^0.8.3` to `^1.9.0` in `package.json`, then run `pnpm install` from the repo root to regenerate `pnpm-lock.yaml` +- [x] 2.2 Confirm tests 1.2 and 1.3 now PASS with no changes to `src/components/GoogleMap.tsx` — if production source edits are required, STOP and re-scope rather than patching around the library +- [x] 2.3 Handle edge cases: get tests 1.5 and 1.6 green +- [x] 2.4 Review each of the six existing `asFragment()` snapshot diffs individually and attribute every change to the 1.x `Map` DOM restructure before regenerating with `pnpm run test -u` +- [x] 2.5 Verify no regressions: tests 1.7 green + +## 3. Refactoring + + + +- [x] 3.1 Extract the shared pin-assertion setup (getter spy install/restore, promise flush) into a local helper in the spec to remove duplication across the new tests +- [x] 3.2 Consolidate marker fixtures so default, image and mixed cases derive from one factory instead of repeating literals +- [x] 3.3 Confirm no unused imports remain (`@googlemaps/jest-mocks` `mockInstances` and `PinElement` are both needed) + +## 4. Verification + +- [x] 4.1 All new tests passing +- [x] 4.2 Full test suite passes with no regressions — `pnpm run test` in `packages/pluggableWidgets/maps-web` (baseline before this change: 90 tests, 12 suites, 6 snapshots) +- [x] 4.3 `tsc --noEmit` clean after the `@types/google.maps` `^3.54.10` → `^3.64.0` move +- [x] 4.4 Build succeeds — `pnpm turbo build` in the widget package +- [x] 4.5 Manual browser check with a real Google Maps API key: default pins render, console shows no `` deprecation warning, and two Maps widgets on one page both initialise (covers the new `@googlemaps/js-api-loader` script path) +- [x] 4.6 Add a CHANGELOG.md entry under `[Unreleased]` describing widget-visible behaviour only — no implementation detail, no dependency names +- [x] 4.7 Code review ready (pending 4.5) + +## Notes + + + +- Do not hand-edit `pnpm-lock.yaml`; it is generated. Repo constraint forbids modifying lockfiles directly, so let `pnpm install` produce it. +- `@mendix/maps-web` is the sole consumer of `@vis.gl/react-google-maps` in the monorepo and no root `pnpm.overrides` entry pins it, so the bump cannot affect other packages. +- Formatting and linting run automatically on edit via Claude Code hooks. Do not invoke `prettier --write` or `pnpm run lint` manually. +- Version bumps in `package.json` `version` field happen at release time, not here. Only the dependency range changes. +- The bump broke `pnpm run build` (`TS2503: Cannot find namespace 'google'`). `1.9.0` bundles its type declarations and dropped the `/// ` that `0.8.3` leaked to consumers. Fixed by adding `@types/google.maps` to `devDependencies` and `"google.maps"` to `tsconfig.json` `types`. Confirmed bump-caused by reverting to `^0.8.3` and rebuilding green. See design.md. +- `1.9.0`'s `APIProvider` calls `google.maps.Settings.getInstance()`, which `@googlemaps/jest-mocks` does not provide. Stubbed in the spec's `beforeEach`. +- Task 4.5 was run manually by the developer on 2026-08-28 against a real Google Maps API key in Studio Pro and passed. It could not be automated from the agent environment. +- Final state: 99 tests / 12 suites / 6 snapshots green (baseline 90), `tsc --noEmit` clean, `pnpm run build` green, no `src/` changes. diff --git a/packages/pluggableWidgets/maps-web/package.json b/packages/pluggableWidgets/maps-web/package.json index 7c82e06b3a..e4194c99c4 100644 --- a/packages/pluggableWidgets/maps-web/package.json +++ b/packages/pluggableWidgets/maps-web/package.json @@ -44,7 +44,7 @@ }, "dependencies": { "@mendix/widget-plugin-mobx-kit": "workspace:*", - "@vis.gl/react-google-maps": "^0.8.3", + "@vis.gl/react-google-maps": "^1.9.0", "brandi": "^5.0.0", "brandi-react": "^5.0.0", "classnames": "^2.5.1", @@ -65,6 +65,7 @@ "@mendix/widget-plugin-platform": "workspace:*", "@mendix/widget-plugin-test-utils": "workspace:*", "@types/deep-equal": "^1.0.1", + "@types/google.maps": "^3.64.0", "@types/leaflet": "^1.9.3", "cross-env": "^7.0.3" } diff --git a/packages/pluggableWidgets/maps-web/src/components/__tests__/GoogleMap.spec.tsx b/packages/pluggableWidgets/maps-web/src/components/__tests__/GoogleMap.spec.tsx index a2a05899a7..9feac6bbb2 100644 --- a/packages/pluggableWidgets/maps-web/src/components/__tests__/GoogleMap.spec.tsx +++ b/packages/pluggableWidgets/maps-web/src/components/__tests__/GoogleMap.spec.tsx @@ -1,8 +1,41 @@ import "@testing-library/jest-dom"; -import { initialize } from "@googlemaps/jest-mocks"; +import { + AdvancedMarkerElement, + initialize, + InfoWindow, + Map as GoogleMapMock, + mockInstances, + PinElement +} from "@googlemaps/jest-mocks"; import { act, render, RenderResult } from "@testing-library/react"; +import { Marker } from "../../../typings/shared"; import { GoogleMapContainer, GoogleMapsProps } from "../GoogleMap"; +/** + * Builds a marker. The default `url` is an empty string because that is what + * `convertAddressToLatLng` produces for markers without a custom marker, which + * routes the marker through the branch of GoogleMap. Pass a `url` to + * take the branch instead. + */ +function createMarker(overrides: Partial = {}): Marker { + return { + latitude: 51.906688, + longitude: 4.48837, + title: "Mendix HQ", + url: "", + ...overrides + }; +} + +const defaultPinMarker = createMarker(); + +const imageMarker = createMarker({ + latitude: 51.922823, + longitude: 4.479632, + title: "Gemeente Rotterdam", + url: "image:url" +}); + describe("Google maps", () => { const defaultProps: GoogleMapsProps = { autoZoom: true, @@ -27,11 +60,40 @@ describe("Google maps", () => { zoomLevel: 10 }; + /** + * Records reads of the deprecated `PinElement.element` property. The mocked + * PinElement has no such property, so the getter returns undefined to stay + * faithful to the mock while still observing access. + */ + let elementGetterSpy: jest.Mock; + let originalElementDescriptor: PropertyDescriptor | undefined; + beforeEach(() => { initialize(); + + // APIProvider reads google.maps.Settings once the API reports loaded, + // and @googlemaps/jest-mocks does not provide it. + (google.maps as unknown as { Settings: { getInstance: () => Partial } }).Settings = { + getInstance: () => ({}) + }; + + elementGetterSpy = jest.fn(); + originalElementDescriptor = Object.getOwnPropertyDescriptor(PinElement.prototype, "element"); + Object.defineProperty(PinElement.prototype, "element", { + configurable: true, + get() { + elementGetterSpy(); + return undefined; + } + }); }); afterEach(() => { + if (originalElementDescriptor) { + Object.defineProperty(PinElement.prototype, "element", originalElementDescriptor); + } else { + delete (PinElement.prototype as unknown as Record).element; + } jest.clearAllMocks(); }); @@ -43,6 +105,70 @@ describe("Google maps", () => { return result!; } + /** + * Lets `useMapsLibrary("marker")` settle. Without this the component + * renders nothing and every pin assertion would pass vacuously. + */ + async function flushMapsLibrary(): Promise { + await act(async () => { + await Promise.resolve(); + }); + } + + function renderedPins(): PinElement[] { + return mockInstances.get(PinElement); + } + + /** + * Pins that ended up in the marker content. vis.gl constructs a fresh + * PinElement on every render because its effect depends on the props object + * identity, so the number of constructed instances tracks render count. + * Only the pin actually attached to the marker is observable behaviour. + */ + function attachedPins(): PinElement[] { + return renderedPins().filter(pin => pin.parentElement !== null); + } + + /** + * Markers still on the map. vis.gl detaches a marker by setting `map` to + * null on cleanup, so this skips instances left over from earlier renders. + */ + function liveMarkers(): AdvancedMarkerElement[] { + return mockInstances.get(AdvancedMarkerElement).filter(marker => Boolean(marker.map)); + } + + /** + * The container vis.gl assigns to `marker.content` and portals children + * into. It is a detached subtree, so marker markup never reaches the + * document and cannot be found with DOM queries or `asFragment()`. + */ + function markerContent(marker: AdvancedMarkerElement): HTMLElement | null { + return marker.content instanceof HTMLElement ? marker.content : null; + } + + function markerImages(): HTMLImageElement[] { + return liveMarkers().flatMap(marker => Array.from(markerContent(marker)?.querySelectorAll("img") ?? [])); + } + + function infoWindowContent(): string { + return mockInstances + .get(InfoWindow) + .flatMap(infoWindow => (infoWindow.setContent as jest.Mock).mock.calls) + .map(([content]) => (content instanceof HTMLElement ? (content.textContent ?? "") : "")) + .join(""); + } + + /** + * Clicks a marker. AdvancedMarker listens for the native `gmp-click` DOM + * event rather than a `google.maps.event` listener, so dispatching on the + * marker element is what reaches the widget's onClick. + */ + async function clickMarker(marker: AdvancedMarkerElement): Promise { + await act(async () => { + marker.dispatchEvent(new CustomEvent("gmp-click")); + }); + } + it("renders a map with right structure", async () => { const { asFragment } = await renderGoogleMap({ heightUnit: "percentageOfWidth", widthUnit: "pixels" }); expect(asFragment()).toMatchSnapshot(); @@ -65,20 +191,7 @@ describe("Google maps", () => { it("renders a map with markers", async () => { const { asFragment } = await renderGoogleMap({ - locations: [ - { - title: "Mendix HQ", - latitude: 51.906688, - longitude: 4.48837, - url: "image:url" - }, - { - title: "Gementee Rotterdam", - latitude: 51.922823, - longitude: 4.479632, - url: "image:url" - } - ] + locations: [createMarker({ url: "image:url" }), imageMarker] }); expect(asFragment()).toMatchSnapshot(); }); @@ -86,12 +199,88 @@ describe("Google maps", () => { it("renders a map with current location", async () => { const { asFragment } = await renderGoogleMap({ showCurrentLocation: true, - currentLocation: { - latitude: 51.906688, - longitude: 4.48837, - url: "image:url" - } + currentLocation: createMarker({ url: "image:url", title: undefined }) }); expect(asFragment()).toMatchSnapshot(); }); + + describe("default pin markers", () => { + it("attaches a pin for a marker without a custom image", async () => { + await renderGoogleMap({ locations: [defaultPinMarker] }); + await flushMapsLibrary(); + + expect(renderedPins().length).toBeGreaterThan(0); + expect(attachedPins()).toHaveLength(1); + }); + + it("does not read the deprecated element property of PinElement", async () => { + await renderGoogleMap({ locations: [defaultPinMarker] }); + await flushMapsLibrary(); + + expect(elementGetterSpy).not.toHaveBeenCalled(); + }); + + it("attaches a pin for the current location marker when it has no image", async () => { + await renderGoogleMap({ showCurrentLocation: true, currentLocation: defaultPinMarker }); + await flushMapsLibrary(); + + expect(attachedPins()).toHaveLength(1); + }); + + it("opens an info window with the marker title when a default pin is clicked", async () => { + await renderGoogleMap({ locations: [defaultPinMarker] }); + await flushMapsLibrary(); + + expect(mockInstances.get(InfoWindow)).toHaveLength(0); + + await clickMarker(liveMarkers()[0]); + await flushMapsLibrary(); + + expect(infoWindowContent()).toContain("Mendix HQ"); + }); + }); + + describe("custom image markers", () => { + it("renders an image and no pin for a marker with a custom image", async () => { + await renderGoogleMap({ locations: [imageMarker] }); + await flushMapsLibrary(); + + expect(markerImages().map(image => image.getAttribute("src"))).toEqual(["image:url"]); + expect(renderedPins()).toHaveLength(0); + }); + + it("renders a pin for the default marker and an image for the custom marker", async () => { + await renderGoogleMap({ locations: [defaultPinMarker, imageMarker] }); + await flushMapsLibrary(); + + expect(attachedPins()).toHaveLength(1); + expect(markerImages()).toHaveLength(1); + }); + + it("calls the marker onClick action when clicked", async () => { + const onClick = jest.fn(); + await renderGoogleMap({ locations: [{ ...imageMarker, onClick }] }); + await flushMapsLibrary(); + + await clickMarker(liveMarkers()[0]); + + expect(onClick).toHaveBeenCalledTimes(1); + }); + }); + + describe("map camera", () => { + it("fits the bounds of all markers when autoZoom is on", async () => { + await renderGoogleMap({ autoZoom: true, locations: [defaultPinMarker, imageMarker] }); + + expect(mockInstances.get(GoogleMapMock)[0].fitBounds).toHaveBeenCalled(); + }); + + it("centers on the marker bounds when autoZoom is off", async () => { + await renderGoogleMap({ autoZoom: false, locations: [defaultPinMarker, imageMarker] }); + + const map = mockInstances.get(GoogleMapMock)[0]; + expect(map.setCenter).toHaveBeenCalled(); + expect(map.fitBounds).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/pluggableWidgets/maps-web/src/components/__tests__/__snapshots__/GoogleMap.spec.tsx.snap b/packages/pluggableWidgets/maps-web/src/components/__tests__/__snapshots__/GoogleMap.spec.tsx.snap index f09ed1a64d..e11fdec0b9 100644 --- a/packages/pluggableWidgets/maps-web/src/components/__tests__/__snapshots__/GoogleMap.spec.tsx.snap +++ b/packages/pluggableWidgets/maps-web/src/components/__tests__/__snapshots__/GoogleMap.spec.tsx.snap @@ -12,7 +12,11 @@ exports[`Google maps renders a map with current location 1`] = `
+ > +
+
@@ -30,7 +34,11 @@ exports[`Google maps renders a map with markers 1`] = `
+ > +
+
@@ -48,7 +56,11 @@ exports[`Google maps renders a map with percentage of parent units renders the s
+ > +
+
@@ -66,7 +78,11 @@ exports[`Google maps renders a map with percentage of width and height units ren
+ > +
+
@@ -84,7 +100,11 @@ exports[`Google maps renders a map with pixels renders structure correctly 1`] =
+ > +
+
@@ -102,7 +122,11 @@ exports[`Google maps renders a map with right structure 1`] = `
+ > +
+
diff --git a/packages/pluggableWidgets/maps-web/tsconfig.json b/packages/pluggableWidgets/maps-web/tsconfig.json index 7aa60df0c9..9146cc92ca 100644 --- a/packages/pluggableWidgets/maps-web/tsconfig.json +++ b/packages/pluggableWidgets/maps-web/tsconfig.json @@ -7,7 +7,7 @@ "module": "esnext", "target": "es6", "lib": ["esnext", "dom"], - "types": ["jest", "node"], + "types": ["jest", "node", "google.maps"], "moduleResolution": "node", "declaration": false, "noLib": false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62508aadd8..44c7a3b8d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -376,7 +376,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 11.12.1 - version: 11.12.1(patch_hash=9081455b6de1f5a4af4d792640f6266f917f9ced2d61aae16e64a379ad11faa2)(@jest/transform@30.3.0)(@jest/types@30.4.1)(@types/babel__core@7.20.5)(@types/node@24.12.4)(canvas@3.2.3)(eslint@9.39.5(jiti@2.6.1))(jest-util@30.4.1)(picomatch@4.0.5)(prettier@3.9.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tslib@2.8.1) + version: 11.12.1(patch_hash=9081455b6de1f5a4af4d792640f6266f917f9ced2d61aae16e64a379ad11faa2)(@jest/transform@30.3.0)(@jest/types@30.4.1)(@types/babel__core@7.20.5)(@types/node@24.12.4)(canvas@3.2.3)(eslint@9.39.5(jiti@2.6.1))(jest-util@30.4.1)(prettier@3.9.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tslib@2.8.1) '@mendix/prettier-config-web-widgets': specifier: workspace:* version: link:../../shared/prettier-config-web-widgets @@ -1817,8 +1817,8 @@ importers: specifier: workspace:* version: link:../../shared/widget-plugin-mobx-kit '@vis.gl/react-google-maps': - specifier: ^0.8.3 - version: 0.8.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.9.0 + version: 1.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) brandi: specifier: ^5.0.0 version: 5.1.0 @@ -1852,7 +1852,7 @@ importers: version: link:../../shared/eslint-config-web-widgets '@mendix/pluggable-widgets-tools': specifier: 11.12.1 - version: 11.12.1(patch_hash=9081455b6de1f5a4af4d792640f6266f917f9ced2d61aae16e64a379ad11faa2)(@jest/transform@30.3.0)(@jest/types@30.4.1)(@types/babel__core@7.20.5)(@types/node@24.12.4)(canvas@3.2.3)(eslint@9.39.5(jiti@2.6.1))(jest-util@30.4.1)(prettier@3.9.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tslib@2.8.1) + version: 11.12.1(patch_hash=9081455b6de1f5a4af4d792640f6266f917f9ced2d61aae16e64a379ad11faa2)(@jest/transform@30.3.0)(@jest/types@30.4.1)(@types/babel__core@7.20.5)(@types/node@24.12.4)(canvas@3.2.3)(eslint@9.39.5(jiti@2.6.1))(jest-util@30.4.1)(picomatch@4.0.5)(prettier@3.9.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tslib@2.8.1) '@mendix/prettier-config-web-widgets': specifier: workspace:* version: link:../../shared/prettier-config-web-widgets @@ -1874,6 +1874,9 @@ importers: '@types/deep-equal': specifier: ^1.0.1 version: 1.0.4 + '@types/google.maps': + specifier: ^3.64.0 + version: 3.65.3 '@types/leaflet': specifier: ^1.9.3 version: 1.9.21 @@ -4149,6 +4152,9 @@ packages: '@googlemaps/jest-mocks@2.22.8': resolution: {integrity: sha512-r0Gh5F/KpDWVgnyQQYTkFbldxY9XUU4FPxv6Gs8nulvbEPR1fvnbTUXEzJp2O1h0RyK2VJLh1jk0mDwhUneFjQ==} + '@googlemaps/js-api-loader@2.1.1': + resolution: {integrity: sha512-yUpAwksbHrlZIWD49JmveNSfBG4oAK0AwMknfSaPMnP5N7UT8oFRVCqwjGb1XQovi//7KLbPQKZpbofiLGzpDw==} + '@happy-dom/jest-environment@19.0.2': resolution: {integrity: sha512-dRX5Xuiwevif8mPQK9EYDxub/Nz6JvPKzIwNv4cIDz8+dwUrAKxJzLmfsKeKImjLPad0zpo+6orUfgadoJCwFQ==} engines: {node: '>=20.0.0'} @@ -4614,7 +4620,6 @@ packages: '@plotly/mapbox-gl@1.13.4': resolution: {integrity: sha512-sR3/Pe5LqT/fhYgp4rT4aSFf1rTsxMbGiH6Hojc7PH36ny5Bn17iVFUjpzycafETURuFbLZUfjODO8LvSI+5zQ==} engines: {node: '>=6.4.0'} - deprecated: This package is deprecated as of August 2026. plotly.js v4 uses MapLibre for map traces — see https://github.com/maplibre/maplibre-gl-js. '@plotly/point-cluster@3.1.9': resolution: {integrity: sha512-MwaI6g9scKf68Orpr1pHZ597pYx9uP8UEFXLPbsCmuw3a84obwz6pnMXGc90VhgDNeNiLEdlmuK7CPo+5PIxXw==} @@ -6066,8 +6071,8 @@ packages: cpu: [x64] os: [win32] - '@vis.gl/react-google-maps@0.8.3': - resolution: {integrity: sha512-iubZIH9MJSkJA9NCMwKkMlHb/iNSeXzVRE7fPVhkKJPId6TBvQcpKA98tirUXi2AfEkYL+IVcE3doL6WdeQ2QA==} + '@vis.gl/react-google-maps@1.9.0': + resolution: {integrity: sha512-ELL/zo8mRMPGDQZwHRClf2tE1/PMYYKjg+C/dUn8rmlQ0e+s7rkcNmCgaeBAV48GJzEcrBsr+vjDv4AvzJrlUA==} peerDependencies: react: '>=18.0.0 <19.0.0' react-dom: '>=18.0.0 <19.0.0' @@ -7493,6 +7498,10 @@ packages: resolution: {integrity: sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==} engines: {node: '>=6.0.0'} + fast-equals@6.0.2: + resolution: {integrity: sha512-sAjhj9ZhOxYCGiNMnZLaucOqf5ZeFnHNoKoAZiD9thhJ0N8RP85qJK759/97C/3L7NzzmGVB5uiX9AUpySZmUQ==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -12216,6 +12225,10 @@ snapshots: '@googlemaps/jest-mocks@2.22.8': {} + '@googlemaps/js-api-loader@2.1.1': + dependencies: + '@types/google.maps': 3.65.3 + '@happy-dom/jest-environment@19.0.2(@jest/environment@30.3.0)(@jest/fake-timers@30.3.0)(@jest/types@30.4.1)(jest-mock@30.4.1)(jest-util@30.4.1)': dependencies: '@jest/environment': 30.3.0 @@ -14385,10 +14398,11 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vis.gl/react-google-maps@0.8.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@vis.gl/react-google-maps@1.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: + '@googlemaps/js-api-loader': 2.1.1 '@types/google.maps': 3.65.3 - fast-deep-equal: 3.1.3 + fast-equals: 6.0.2 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -16094,6 +16108,8 @@ snapshots: fast-equals@5.4.1: {} + fast-equals@6.0.2: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 From 4f668303e08dd4a5c47d4c5a12186f608c982715 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Fri, 28 Aug 2026 16:20:30 +0200 Subject: [PATCH 2/2] fix: broken default markers on maps --- .../pluggableWidgets/maps-web/CHANGELOG.md | 2 ++ .../maps-web/rollup.config.mjs | 19 +------------------ 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/packages/pluggableWidgets/maps-web/CHANGELOG.md b/packages/pluggableWidgets/maps-web/CHANGELOG.md index b67caf44a7..227a54d7eb 100644 --- a/packages/pluggableWidgets/maps-web/CHANGELOG.md +++ b/packages/pluggableWidgets/maps-web/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - We fixed an issue where the browser console showed a deprecation warning for every default marker on a Google Maps map. +- We fixed an issue where default markers appeared as broken images in deployed apps while rendering correctly during local development. + ### Changed - We replaced the react-leaflet dependency with a direct Leaflet integration due to licensing considerations. diff --git a/packages/pluggableWidgets/maps-web/rollup.config.mjs b/packages/pluggableWidgets/maps-web/rollup.config.mjs index d355d4ad4f..688a1a7197 100644 --- a/packages/pluggableWidgets/maps-web/rollup.config.mjs +++ b/packages/pluggableWidgets/maps-web/rollup.config.mjs @@ -1,22 +1,5 @@ -import { mkdirSync } from "node:fs"; -import { fileURLToPath } from "url"; import copyFiles from "@mendix/rollup-web-widgets/copyFiles.mjs"; export default args => { - const result = copyFiles(args); - - const [jsConfig, mJsConfig] = result; - - const folderUrl = new URL("dist/tmp/widgets/com/mendix/widget/custom/Maps/", import.meta.url); - const folderPath = fileURLToPath(folderUrl); - - // create target dir before any bundling to make sure casing is correct: - // expected: com/mendix/widget/custom/Maps - mkdirSync(folderPath, { recursive: true }); - - // We change the output because maps widget package was wrongly named with uppercase M in the past. - jsConfig.output.file = fileURLToPath(new URL("Maps.js", folderUrl)); - mJsConfig.output.file = fileURLToPath(new URL("Maps.mjs", folderUrl)); - - return result; + return copyFiles(args); };