Google jsdom 中加载的地图缺少地图图块和控件
Google Map loaded in jsdom is missing map tiles and controls
我正在尝试获取要在 jsdom so I can test map/mouse events with react-testing-library 中加载的 Google 地图。遗憾的是,<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>
也许它在到达 <iframe>
时遇到错误(因为地图控件应该在那之后出现),但我还没有找到进一步调试问题的方法。有什么方法可以让这个地图在 jsdom?
中完全加载
maps google 如何决定是否创建地图:
Google 地图在确保容器可见且大小 > 0 之前不会装载地图(及其控件)。
(您可以通过在 .html
文件中写入 html 并为容器设置 height: 0
样式来验证它。然后您会看到它不会创建地图(使用 inspector
) 直到你设置 height: 400px
.)
jsdom pretendToBeVisual: true
的工作原理:
jsdom pretendToBeVisual: true
并没有真正渲染任何东西。所以 getBoundingClientRect
、clientHeight
和 clientWidth
以及...将 return 0
.
Jsdon/Google-maps 渲染问题:
当您同时使用 googl-maps
和 jsdom
时:google-maps
将加载,但不会创建实际地图,因为它认为容器大小为 0 或者不是可见。
解决方案:
我试图覆盖与 size
相关的 DomElement 方法和属性,例如:offsetHeight
和 clientHeight
以及 getBoundingClientRect
(对于所有元素),但是由于有很多attributes/methods和size有关,全部覆盖很费时间,不是明智的选择。 (请注意,要覆盖 offsetHeight
等只读属性,您不能使用普通的覆盖方法。相反,您应该使用 Object.defineProperty
来覆盖它。)
所以我建议尝试寻找具有渲染支持的 jsdom
的替代方案(或使用在真实浏览器上执行的测试库)/或尝试阅读 google-maps
源代码并找到了解如何 resize
事件处理程序检查容器是否可见,并覆盖所有这些方法。
(如果您不关心是否是最新的,您也可以尝试检查是否可以找到旧版本的 google-maps,它始终呈现地图。)
感谢 @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 });
});
});
我正在尝试获取要在 jsdom so I can test map/mouse events with react-testing-library 中加载的 Google 地图。遗憾的是,<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>
也许它在到达 <iframe>
时遇到错误(因为地图控件应该在那之后出现),但我还没有找到进一步调试问题的方法。有什么方法可以让这个地图在 jsdom?
maps google 如何决定是否创建地图:
Google 地图在确保容器可见且大小 > 0 之前不会装载地图(及其控件)。
(您可以通过在 .html
文件中写入 html 并为容器设置 height: 0
样式来验证它。然后您会看到它不会创建地图(使用 inspector
) 直到你设置 height: 400px
.)
jsdom pretendToBeVisual: true
的工作原理:
jsdom pretendToBeVisual: true
并没有真正渲染任何东西。所以 getBoundingClientRect
、clientHeight
和 clientWidth
以及...将 return 0
.
Jsdon/Google-maps 渲染问题:
当您同时使用 googl-maps
和 jsdom
时:google-maps
将加载,但不会创建实际地图,因为它认为容器大小为 0 或者不是可见。
解决方案:
我试图覆盖与 size
相关的 DomElement 方法和属性,例如:offsetHeight
和 clientHeight
以及 getBoundingClientRect
(对于所有元素),但是由于有很多attributes/methods和size有关,全部覆盖很费时间,不是明智的选择。 (请注意,要覆盖 offsetHeight
等只读属性,您不能使用普通的覆盖方法。相反,您应该使用 Object.defineProperty
来覆盖它。)
所以我建议尝试寻找具有渲染支持的 jsdom
的替代方案(或使用在真实浏览器上执行的测试库)/或尝试阅读 google-maps
源代码并找到了解如何 resize
事件处理程序检查容器是否可见,并覆盖所有这些方法。
(如果您不关心是否是最新的,您也可以尝试检查是否可以找到旧版本的 google-maps,它始终呈现地图。)
感谢 @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 });
});
});