@@ -24,6 +24,9 @@ import {
2424
2525function installBrowserDomParserShim ( ) {
2626 // This minimal shim supports selector/removal behavior only; fixtures must not include scripts.
27+ const hadWindow = Object . hasOwn ( globalThis , 'window' ) ;
28+ const hadDocument = Object . hasOwn ( globalThis , 'document' ) ;
29+ const hadDOMParser = Object . hasOwn ( globalThis , 'DOMParser' ) ;
2730 const previous = {
2831 window : globalThis . window ,
2932 document : globalThis . document ,
@@ -43,6 +46,21 @@ function installBrowserDomParserShim() {
4346 } ,
4447 } ;
4548 Object . defineProperties ( node , {
49+ cheerioElement : {
50+ value : element ,
51+ } ,
52+ parentNode : {
53+ get : ( ) => {
54+ if ( ! element || ! $ ( element ) . parent ( ) . length ) {
55+ return null ;
56+ }
57+ return {
58+ insertBefore ( child , reference ) {
59+ $ ( reference . cheerioElement ) . before ( child . cheerioElement ) ;
60+ } ,
61+ } ;
62+ } ,
63+ } ,
4664 textContent : {
4765 get : ( ) => ( element ? $ ( element ) . text ( ) : $ ( 'html' ) . text ( ) ) ,
4866 } ,
@@ -65,9 +83,12 @@ function installBrowserDomParserShim() {
6583 } ;
6684
6785 return ( ) => {
68- globalThis . window = previous . window ;
69- globalThis . document = previous . document ;
70- globalThis . DOMParser = previous . DOMParser ;
86+ if ( hadWindow ) globalThis . window = previous . window ;
87+ else delete globalThis . window ;
88+ if ( hadDocument ) globalThis . document = previous . document ;
89+ else delete globalThis . document ;
90+ if ( hadDOMParser ) globalThis . DOMParser = previous . DOMParser ;
91+ else delete globalThis . DOMParser ;
7192 } ;
7293}
7394
@@ -381,15 +402,20 @@ describe('HTML Visibility Analyzer', () => {
381402 <p>Encuentra tu estación más cercana.</p>
382403 <div id="map-container">
383404 <div class="gm-style">
384- <div class="gm-style-iw">Map data ©2026 Google</div>
405+ <div class="gm-style-iw">
406+ <div class="gm-style-iw-d">
407+ <h2>Estación Repsol Centro</h2>
408+ <p>Calle Mayor 1, Madrid</p>
409+ </div>
410+ </div>
385411 <div class="gm-bundled-control">
386412 <div class="gmnoprint">
387413 <div class="gm-svpc">Pegman</div>
388414 <div>40.4168° N, 3.7038° W</div>
389415 <button>Zoom in</button>
390416 </div>
391417 </div>
392- <div class="gm-style-cc">Terms of Use Report a map error</div>
418+ <div class="gm-style-cc">Map data ©2026 Google Terms of Use Report a map error</div>
393419 </div>
394420 </div>
395421 <p>Horario: Lunes a viernes 8:00 - 20:00</p>
@@ -400,6 +426,8 @@ describe('HTML Visibility Analyzer', () => {
400426 expect ( text ) . to . include ( 'Estaciones de servicio' ) ;
401427 expect ( text ) . to . include ( 'Encuentra tu estación más cercana.' ) ;
402428 expect ( text ) . to . include ( 'Horario' ) ;
429+ expect ( text ) . to . include ( 'Estación Repsol Centro' ) ;
430+ expect ( text ) . to . include ( 'Calle Mayor 1, Madrid' ) ;
403431 expect ( text ) . to . not . include ( 'Map data' ) ;
404432 expect ( text ) . to . not . include ( 'Pegman' ) ;
405433 expect ( text ) . to . not . include ( '40.4168' ) ;
@@ -426,12 +454,16 @@ describe('HTML Visibility Analyzer', () => {
426454 <span>Keyboard shortcuts</span>
427455 </div>
428456 </div>
457+ <div class="gm-style-cc">Detached attribution</div>
458+ <div class="gm-bundled-control">Detached bundled control</div>
459+ <div class="gm-svpc">Detached Street View control</div>
460+ <div class="gmnoprint">Detached non-printing control</div>
429461 </div>
430462 </div>
431463 <p>Productos Diesel e+ Agrodiesel e+10</p>
432464 </body></html>` ;
433465
434- const text = await stripTagsToText ( html , true ) ;
466+ const text = await stripTagsToText ( html , false ) ;
435467
436468 expect ( text ) . to . include ( 'Buscador de Agencias Distribuidoras de gasóleo' ) ;
437469 expect ( text ) . to . include ( 'GASOLEOS PECES E HIJOS S.L.' ) ;
@@ -442,6 +474,10 @@ describe('HTML Visibility Analyzer', () => {
442474 expect ( text ) . to . not . include ( 'Report a map error' ) ;
443475 expect ( text ) . to . not . include ( 'Zoom in' ) ;
444476 expect ( text ) . to . not . include ( 'Keyboard shortcuts' ) ;
477+ expect ( text ) . to . not . include ( 'Detached attribution' ) ;
478+ expect ( text ) . to . not . include ( 'Detached bundled control' ) ;
479+ expect ( text ) . to . not . include ( 'Detached Street View control' ) ;
480+ expect ( text ) . to . not . include ( 'Detached non-printing control' ) ;
445481 } ) ;
446482
447483 it ( 'should avoid removing customer-authored Google Maps wrapper content' , async ( ) => {
@@ -459,6 +495,17 @@ describe('HTML Visibility Analyzer', () => {
459495 <div data-google-maps>
460496 <span>Customer-authored store locator content.</span>
461497 </div>
498+ <gmp-map>
499+ <gmp-advanced-marker>
500+ <span>Madrid showroom: Calle de Alcalá 10.</span>
501+ </gmp-advanced-marker>
502+ </gmp-map>
503+ <div class="custom-gm-style">
504+ <span>Customer content with a similar class name.</span>
505+ </div>
506+ <div class="gmnoprint-content">
507+ <span>Printable customer directions.</span>
508+ </div>
462509 <p>Visit us at our office.</p>
463510 </body></html>` ;
464511
@@ -470,6 +517,9 @@ describe('HTML Visibility Analyzer', () => {
470517 expect ( text ) . to . include ( 'Our office locations are shown on a Google map.' ) ;
471518 expect ( text ) . to . include ( 'Store locator fallback copy for visitors.' ) ;
472519 expect ( text ) . to . include ( 'Customer-authored store locator content.' ) ;
520+ expect ( text ) . to . include ( 'Madrid showroom: Calle de Alcalá 10.' ) ;
521+ expect ( text ) . to . include ( 'Customer content with a similar class name.' ) ;
522+ expect ( text ) . to . include ( 'Printable customer directions.' ) ;
473523 } ) ;
474524
475525 it ( 'should remove Google Maps widgets in browser DOMParser mode' , async ( ) => {
@@ -480,18 +530,22 @@ describe('HTML Visibility Analyzer', () => {
480530 <div class="gm-style">
481531 <div>Sorry, we have no imagery here.</div>
482532 <div class="gm-style-cc">Map Data Terms</div>
533+ <div class="gm-style-iw">Open Madrid location details.</div>
483534 </div>
484- <gmp-map>Zoom in Map data ©Google</gmp-map>
535+ <gmp-map>
536+ <gmp-advanced-marker>Customer-authored marker details.</gmp-advanced-marker>
537+ </gmp-map>
485538 <p>Real office content.</p>
486539 </body></html>` ;
487540
488541 const text = await filterHtmlContent ( html , true , true ) ;
489542
490543 expect ( text ) . to . include ( 'Office locations' ) ;
491544 expect ( text ) . to . include ( 'Real office content.' ) ;
545+ expect ( text ) . to . include ( 'Open Madrid location details.' ) ;
546+ expect ( text ) . to . include ( 'Customer-authored marker details.' ) ;
492547 expect ( text ) . to . not . include ( 'Sorry, we have no imagery here' ) ;
493548 expect ( text ) . to . not . include ( 'Map Data' ) ;
494- expect ( text ) . to . not . include ( 'Zoom in' ) ;
495549 } finally {
496550 restoreBrowser ( ) ;
497551 }
@@ -549,6 +603,24 @@ describe('HTML Visibility Analyzer', () => {
549603 } ) ;
550604
551605 describe ( 'generateMarkdownDiff + getAddedMarkdownBlocks' , ( ) => {
606+ it ( 'should exclude Google Maps runtime text from rendered markdown' , async ( ) => {
607+ const serverHtml = '<html><body><h1>Locations</h1></body></html>' ;
608+ const clientHtml = `<html><body>
609+ <h1>Locations</h1>
610+ <div class="gm-style">
611+ <div>Sorry, we have no imagery here.</div>
612+ <div class="gm-style-cc">Map data ©2026 Google</div>
613+ </div>
614+ <p>Madrid office details.</p>
615+ </body></html>` ;
616+
617+ const { currentRenderedHtml } = await generateMarkdownDiff ( serverHtml , clientHtml , false ) ;
618+
619+ expect ( currentRenderedHtml ) . to . include ( 'Madrid office details.' ) ;
620+ expect ( currentRenderedHtml ) . to . not . include ( 'Sorry, we have no imagery here.' ) ;
621+ expect ( currentRenderedHtml ) . to . not . include ( 'Map data' ) ;
622+ } ) ;
623+
552624 it ( 'should not show JSON-LD as added when content is identical but DOM nesting differs' , async ( ) => {
553625 const jsonLd = JSON . stringify ( { '@context' : 'https://schema.org' , '@type' : 'WebPage' , name : 'Test' } ) ;
554626 // Server-side: skip button has 3-space indentation (from DOM nesting)
0 commit comments