我想测试一些自定义的Web组件,并使用jest.js作为测试运行器(由于其对ES6的支持)。
Chromium支持类似的命令
window.customElements.define('my-custom-element', MyCustomElementClass);
注册自定义Web组件。
但是,在笑话测试的背景下,window.customElements
似乎并不为人所知。
作为解决方法,我尝试将笑话与puppeteer结合使用并表达来运行Chromium中的customElements
部分。
但是,我很难将自定义元素类TreezElement
注入评估的代码中:
treezElement.js:
class TreezElement extends HTMLElement {
connectedCallback () {
this.innerHTML = 'Hello, World!';
}
}
treezElement.test.js:
import TreezElement from '../../src/components/treezElement.js';
import puppeteer from 'puppeteer';
import express from 'express';
describe('Construction', ()=>{
let port = 3000;
let browser;
let page;
let element;
const width = 800;
const height = 800;
beforeAll(async () => {
const app = await express()
.use((req, res) => {
res.send(
`<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
</body>
</html>`
)
})
.listen(port);
browser = await puppeteer.launch({
headless: false,
slowMo: 80,
args: [`--window-size=${width},${height}`]
});
var pages = await browser.pages();
page = pages[0];
await page.setViewport({ width, height });
await page.goto('http://localhost:3000');
element = await page.evaluate(({TreezElement}) => {
console.log('TreezElement:')
console.log(TreezElement);
window.customElements.define('treez-element', TreezElement);
var element = document.create('treez-element');
document.body.appendChild(element);
return element;
}, {TreezElement});
});
it('TreezElement', ()=>{
});
afterAll(() => {
browser.close();
});
});
也许TreezElement
无法序列化,因此undefined
被传递给函数。
如果我尝试直接从评估的代码中导入自定义元素类TreezElement
...
element = await page.evaluate(() => {
import TreezElement from '../../src/components/treezElement.js';
console.log('TreezElement:')
console.log(TreezElement);
window.customElements.define('treez-element', TreezElement);
var element = document.create('treez-element');
document.body.appendChild(element);
return element;
});
...我收到错误消息
“导入”和“导出”可能仅出现在顶层
=>用玩笑来测试自定义Web组件的推荐方法是什么?
一些相关的东西:
答案 0 :(得分:2)
JSDOM 16.2包括对自定义元素的基本支持,并且在Jest 26.5及更高版本中可用。这是一个简单的Jest测试,表明它可以正常工作:
customElements.define('test-component', class extends HTMLElement {
constructor() {
super();
const p = document.createElement('p')
p.textContent = 'It works!'
this.appendChild(p)
}
})
test('custom elements in JSDOM', () => {
document.body.innerHTML = `<h1>Custom element test</h1> <test-component></test-component>`
expect(document.body.innerHTML).toContain('It works!')
})
输出:
$ jest
PASS ./test.js
✓ custom elements in JSDOM (11 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.409 s
Ran all test suites.
请注意,并非所有功能都受支持,尤其是阴影DOM不起作用。
答案 1 :(得分:0)
这是一个丑陋的版本,可以正常工作。关于此的一些进一步说明:
express.js配置为充当文件服务器。否则,对于导入的ES6模块,mime类型或跨源检查会存在问题。
类TreezElement
不会直接导入,而是通过创建额外脚本标签
实例方法与代码覆盖率有关。似乎不可能直接调用TreezElement
的构造函数(继承自HTMLElement,=> illegal constructor
)。 元素类的实例只能用伪造者中的document.createElement(...)
创建。因此,不能在Jest中测试所有实例方法,而只能在静态方法中测试。实例方法和属性可以在puppeteer中进行测试。但是,the code coverage of jest does not consider the code coverage of puppeteer。
创建的TreezElement
类型的元素可以以ElementHandle的形式返回。 访问element实例的属性和方法很麻烦(请参见下面的示例)。作为处理方法的替代方法,可以应用page.$eval
方法:
var id = await page.$eval('#custom-element', element=> element.id);
index.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="root"></div>
</body>
</html>
treezElement.test.js
import TreezElement from '../../src/components/treezElement.js';
import puppeteer from 'puppeteer';
import express from 'express';
describe('Construction', ()=>{
let port = 4444;
const index = Math.max(process.argv.indexOf('--port'), process.argv.indexOf('-p'))
if (index !== -1) {
port = +process.argv[index + 1] || port;
}
var elementHandle;
beforeAll(async () => {
const fileServer = await express()
.use(express.static('.'))
.listen(port);
var browser = await puppeteer.launch({
headless: false,
slowMo: 80,
userDataDir: '.chrome',
args: ['--auto-open-devtools-for-tabs']
});
var pages = await browser.pages();
var page = pages[0];
await page.goto('http://localhost:'+port + '/test/index.html');
await page.evaluate(() => {
var script = document.createElement('script');
script.type='module';
script.innerHTML="import TreezElement from '../src/components/treezElement.js';\n" +
"window.customElements.define('treez-element', TreezElement);\n" +
"var element = document.createElement('treez-element');\n" +
"element.id='custom-element';\n" +
"document.body.appendChild(element);";
document.head.appendChild(script);
});
elementHandle = await page.evaluateHandle(() => {
return document.getElementById('custom-element');
});
});
it('id', async ()=>{
var idHandle = await elementHandle.getProperty('id');
var id = await idHandle.jsonValue();
expect(id).toBe('custom-element');
});
afterAll(() => {
browser.close();
});
});
答案 2 :(得分:0)
另一种(有限的)方法是使用Object.create
来创建自定义Web组件的实例,而不使用window.customElements.define
和document.createElement(..)
:
import TreezElement from '../../src/components/treezElement.js';
var customElement = Object.create(TreezElement.prototype);
通过这种方式,可以直接测试实例方法,并且这些测试也包含在代码覆盖率中。 (有关伪造者的其他答案也涵盖了这个问题。)
一个主要缺点是只能访问方法,而不能访问属性。如果我尝试使用customElement.customProperty,我将得到:TypeError: Illegal invocation
。
这是由Element.js中的检查!module.exports.is(this)
引起的:
Element.prototype.getAttribute = function getAttribute(qualifiedName) {
if (!this || !module.exports.is(this)) {
throw new TypeError("Illegal invocation");
}
...
Element.prototype.setAttribute = function setAttribute(qualifiedName,value){
if (!this || !module.exports.is(this)) {
throw new TypeError("Illegal invocation");
}
Object.create
的另一个缺点是未调用构造函数代码,也不将其包括在测试范围内。
如果命令window.customElements.define(..)
直接包含在我们要导入的类文件中(例如treezElement.js)... customElements
属性需要在包含导入之前进行模拟:< / p>
customElementsMock.js:
export default class CustomElementsMock{} //dummy export
//following command mocks the customElements feature to be able
//to import custom elements in jest tests
window.customElements = {
define: (elementName, elementClass)=>{
console.log('Mocked customElements.define(..) for custom element "' + elementName + '"');
}
};
在treezElement.test.js中的用法:
import CustomElementsMock from '../customElementsMock.js';
import TreezElement from '../../src/components/treezElement.js';
var customElement = Object.create(TreezElement.prototype);
//...
(我还尝试将模拟代码直接放在treezElement.test.js
的开头,但all the imports are executed prior to the script doing the import。这就是为什么我不得不将模拟代码放在一个额外的文件中的原因。)
答案 3 :(得分:0)
我创建了一个DOM,它支持Web组件的服务器端渲染。它还支持使用Jest测试Web组件。
DOM:
https://www.npmjs.com/package/happy-dom
最佳环境:
https://www.npmjs.com/package/jest-environment-happy-dom
要安装
npm install jest-environment-happy-dom --save-dev
要使用它:
编辑您的package.json以包括Jest环境:
{
"scripts": {
"test": "jest --env=jest-environment-happy-dom"
}
}
答案 4 :(得分:0)
使用电子流道可以包括所有节点和铬环境, 用它代替jsdom