【问题标题】:Google Map loaded in jsdom is missing map tiles and controlsjsdom 中加载的谷歌地图缺少地图图块和控件
【发布时间】:2023-03-03 17:22:02
【问题描述】:

我正在尝试在 jsdom 中加载 Google 地图,以便我可以使用 react-testing-library 测试地图/鼠标事件。不幸的是,<img> 地图图块和<button> 地图控件没有加载到 dom 中。我没有从 Google 地图或 jsdom 收到任何错误消息,所以我不确定是什么问题。

我正在使用以下软件包:

canvas@2.6.1
jest@26.1.0
jsdom@16.2.2
@testing-library/jest-dom@5.11.0
@testing-library/react@10.4.4

这是一个基于 Google 的 synchronous map loading 示例(API 密钥已编辑)的最小示例:

import { JSDOM } from 'jsdom';
import '@testing-library/jest-dom/extend-expect';
import { render, waitFor } from '@testing-library/react';

const dom = new JSDOM(`
  <!DOCTYPE html>
  <html>
    <head>
      <title>Synchronous Loading</title>
      <meta name="viewport" content="initial-scale=1.0">
      <meta charset="utf-8">
      <style>
        #map {
          height: 100%;
        }
      </style>
    </head>
    <body>
      <div id="map"></div>
      <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>
      <script>
        var map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: -34.397, lng: 150.644},
          zoom: 8
        });
      </script>
    </body>
  </html>
`, {
  pretendToBeVisual: true,
  resources: 'usable',
  runScripts: 'dangerously',
});

global.window = dom.window;
global.document = dom.window.document;

describe('Google Map', () => {
  it('has a zoom button', async () => {
    const { queryByRole } = render();  // just checking the dom
    await waitFor(() => expect(queryByRole('button', { name: 'Zoom in' })).toBeInTheDocument(), { timeout: 5000 });
  });
});

此测试因超时而失败。调试器显示地图 div 具有以下内容,其中缺少很多应有的内容(地图图块、地图控件等):

<div id="map" style="position: relative; overflow: hidden;">
  <div style="height: 100%; width: 100%; position: absolute; top: 0px; left: 0px; background-color: rgb(229, 227, 223);">
    <div style="overflow: hidden;"/>
    <div class="gm-style" style="position: absolute; z-index: 0; left: 0px; top: 0px; height: 100%; width: 100%; padding: 0px; border-width: 0px; margin: 0px;">
      <div style="position: absolute; z-index: 0; left: 0px; top: 0px; height: 100%; width: 100%; padding: 0px; border-width: 0px; margin: 0px;cursor: url(https://maps.gstatic.com/mapfiles/openhand_8_8.cur), default;" tabindex="0">
        <div style="z-index: 1; position: absolute; left: 50%; top: 50%; width: 100%; transform: translate(0px,0px);">
          <div style="position: absolute; left: 0px; top: 0px; z-index: 100; width: 100%;">
            <div style="position: absolute; left: 0px; top: 0px; z-index: 0;">
              <div style="position: absolute; z-index: 992; transform: matrix(1,0,0,1,-32,-20);">
                <div style="position: absolute; left: 0px; top: 0px; width: 256px; height: 256px;">
                  <div style="width: 256px; height: 256px;" />
                </div>
              </div>
            </div>
          </div>
          <div style="position: absolute; left: 0px; top: 0px; z-index: 101; width: 100%;" />
          <div style="position: absolute; left: 0px; top: 0px; z-index: 102; width: 100%;" />
          <div style="position: absolute; left: 0px; top: 0px; z-index: 103; width: 100%;" />
          <div style="position: absolute; left: 0px; top: 0px; z-index: 0;" />
        </div>
        <div class="gm-style-pbc" style="z-index: 2; position: absolute; height: 100%; width: 100%; padding: 0px; border-width: 0px; margin: 0px left: 0px; top: 0px; transition-duration: 0; opacity: 0;">
          <p class="gm-style-pbt" />
        </div>
        <div style="z-index: 3; position: absolute; height: 100%; width: 100%; padding: 0px; border-width: 0px; margin: 0px; left: 0px; top: 0px;">
          <div style="z-index: 4; position: absolute; left: 50%; top: 50%; width: 100%; transform: translate(0px,0px);">
            <div style="position: absolute; left: 0px; top: 0px; z-index: 104; width: 100%;" />
            <div style="position: absolute; left: 0px; top: 0px; z-index: 105; width: 100%;" />
            <div style="position: absolute; left: 0px; top: 0px; z-index: 106; width: 100%;" />
            <div style="position: absolute; left: 0px; top: 0px; z-index: 107; width: 100%;" />
          </div>
        </div>
      </div>
      <iframe aria-hidden="true" frameborder="0" style="z-index: -1; position: absolute; width: 100%; height: 100%; top: 0px; left: 0px;" tabindex="-1" />
    </div>
  </div>
</div>

也许它在到达&lt;iframe&gt; 时遇到了错误(因为地图控件应该在那之后出现),但我还没有找到进一步调试问题的方法。有什么办法可以让这张地图在 jsdom 中完全加载?

【问题讨论】:

    标签: javascript google-maps-api-3 jestjs react-testing-library jsdom


    【解决方案1】:

    感谢@yaya 的回答,我能够通过模拟与大小相关的属性和方法来安装地图。添加以下代码,按钮测试通过:

    const mockWidth = 200;
    const mockHeight = 200;
    
    function shouldMockRender(element) {
      const map = dom.window.document.getElementById('map');
      const isMap = element.id === 'map';
      const isChild = map && map.contains(element);
      return isMap || isChild;
    }
    
    Object.defineProperty(dom.window.HTMLElement.prototype, 'clientWidth', {
      get() {
        return shouldMockRender(this) ? mockWidth : 0;
      },
    });
    
    Object.defineProperty(dom.window.HTMLElement.prototype, 'clientHeight', {
      get() {
        return shouldMockRender(this) ? mockHeight : 0;
      },
    });
    
    dom.window.HTMLElement.prototype.getBoundingClientRect = function getBoundingClientRect() {
      return {
        x: 0,
        y: 0,
        width: this.clientWidth,
        height: this.clientHeight,
        top: 0,
        right: this.clientWidth,
        bottom: this.clientHeight,
        left: 0,
      };
    };
    

    顺便说一句,即使在测试完成后,该进程也会继续运行。关闭window 修复了:

    describe('Google Map', () => {
      afterAll(() => window.close());
    
      it('has a zoom button', async () => {
        const { queryByRole } = render(); // just checking the dom
        await waitFor(() => expect(queryByRole('button', { name: 'Zoom in' })).toBeInTheDocument(), { timeout: 5000 });
      });
    });
    

    【讨论】:

      【解决方案2】:

      谷歌地图如何决定是否创建地图:

      Google 地图在确保容器可见且大小 > 0 之前不会装载地图(及其控件)。

      (您可以通过在.html 文件中编写您的html 并为容器设置height: 0 样式来验证它。然后您会看到它不会创建地图(使用inspector),直到您设置height: 400px .)

      jsdom pretendToBeVisual: true 的工作原理:

      jsdom pretendToBeVisual: true 并没有真正渲染任何东西。所以getBoundingClientRectclientHeightclientWidth 和...将返回0

      Jsdon/Google-maps 渲染问题:

      当您同时使用googl-mapsjsdom 时:google-maps 将加载,但它不会创建实际的地图,因为它认为容器大小为 0 或不可见。

      解决方案:

      我试图覆盖与 size 相关的 DomElement 方法和属性,例如:offsetHeightclientHeightgetBoundingClientRect(适用于所有元素),但由于有很多与大小相关的属性/方法,而且覆盖所有这些是耗时的,这不是一个明智的选择。 (请注意,要覆盖像offsetHeight 这样的只读属性,您不能使用正常的覆盖方法。相反,您应该使用:Object.defineProperty 覆盖它。)

      所以我建议尝试寻找具有渲染支持的jsdom 的替代品(或使用在真实浏览器上执行的测试库)/或尝试阅读google-maps 源代码并了解resize 事件如何发生处理程序检查容器是否可见,并覆盖所有这些方法。

      (如果您不关心最新版本,也可以尝试检查是否可以找到始终呈现地图的旧版 google-maps。)

      【讨论】:

      • 感谢您的帮助!我以前使用 puppeteer,但我希望切换到 jsdom,这样我就可以在不运行测试服务器的情况下测试地图。我可以通过模拟您建议的属性/方法来安装它。我已经发布了我的解决方案,以防它对其他人有所帮助。
      猜你喜欢
      • 2015-04-22
      • 2018-08-31
      • 1970-01-01
      • 1970-01-01
      • 2017-08-01
      • 1970-01-01
      • 2015-01-14
      • 2013-08-11
      • 1970-01-01
      相关资源
      最近更新 更多