From 3e8fce3b36deb617cb645e6ebfae19a1dea9a839 Mon Sep 17 00:00:00 2001 From: simonyang08 Date: Thu, 3 Sep 2026 21:56:13 +0800 Subject: [PATCH] fix(advanced-marker): only assign anchorLeft/anchorTop when provided (#867) When the consumer supplied only one of `anchorLeft` or `anchorTop`, `useAdvancedMarkerAnchoring` unconditionally assigned `undefined` to the other side, clobbering the default value the Google Maps JS API had set up on the AdvancedMarkerElement. Combined with `gmpDraggable = true`, the API subsequently threw `TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'` while wiring up the drag handle (issue #867). The fix only assigns the property when the consumer actually provided it, leaving the unprovided side untouched. Adds a regression test that verifies the unprovided anchor side has no own property descriptor after rendering. Signed-off-by: simonyang08 --- .../__tests__/advanced-marker.test.tsx | 48 +++++++++++++++++++ src/components/advanced-marker.tsx | 10 +++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/components/__tests__/advanced-marker.test.tsx b/src/components/__tests__/advanced-marker.test.tsx index e6727436..92bdbc24 100644 --- a/src/components/__tests__/advanced-marker.test.tsx +++ b/src/components/__tests__/advanced-marker.test.tsx @@ -253,6 +253,54 @@ describe('map and marker-library loaded', () => { (google.maps as any).version = '3.62.9'; }); + // Regression test for issue #867: when the consumer provides only + // `anchorTop` (or only `anchorLeft`), the library must not assign + // `undefined` to the unspecified side. Doing so clobbers the + // default value the Google Maps JS API sets up, and combined with + // `gmpDraggable = true` the API subsequently throws + // `TypeError: Failed to execute 'appendChild' on 'Node'` while + // wiring up the drag handle. + test('only the provided anchor prop is assigned, the other side is left untouched (#867)', async () => { + // The mock AdvancedMarkerElement is a plain class extending the + // jest-mocks HTMLElement; it does not pre-define `anchorLeft` / + // `anchorTop` on instances. Therefore, when the library assigns + // `undefined` to the unprovided side, an own property with + // value `undefined` is created. After the fix, that own property + // must NOT exist because the library must not touch the side the + // consumer did not provide. + const {unmount} = render( + +
+ + ); + + const marker = await waitForMockInstance( + google.maps.marker.AdvancedMarkerElement + ); + expect(marker.anchorTop).toBe('-90%'); + // After the fix: anchorLeft must remain unset (no own property). + expect( + Object.getOwnPropertyDescriptor(marker, 'anchorLeft') + ).toBeUndefined(); + + unmount(); + + render( + +
+ + ); + + const marker2 = await waitForMockInstance( + google.maps.marker.AdvancedMarkerElement + ); + expect(marker2.anchorLeft).toBe('10px'); + // After the fix: anchorTop must remain unset (no own property). + expect( + Object.getOwnPropertyDescriptor(marker2, 'anchorTop') + ).toBeUndefined(); + }); + test('anchorLeft/anchorTop should have precedence over anchorPoint', async () => { const consoleWarnSpy = jest .spyOn(console, 'warn') diff --git a/src/components/advanced-marker.tsx b/src/components/advanced-marker.tsx index 66133a50..3d9efa4f 100644 --- a/src/components/advanced-marker.tsx +++ b/src/components/advanced-marker.tsx @@ -365,8 +365,14 @@ function useAdvancedMarkerAnchoring( ); } - marker.anchorLeft = anchorLeft; - marker.anchorTop = anchorTop; + // Only assign properties that were actually provided by the consumer. + // Assigning `undefined` to `anchorLeft`/`anchorTop` would clobber the + // element's default value, and the Google Maps JS API can subsequently + // fail with a `TypeError: Failed to execute 'appendChild' on 'Node'` + // when it later reads these properties while setting up drag handles + // (see issue #867). + if (anchorLeft !== undefined) marker.anchorLeft = anchorLeft; + if (anchorTop !== undefined) marker.anchorTop = anchorTop; // when anchorLeft and/or anchorTop are set, we'll ignore the anchorPoint if (anchorPoint !== undefined) {