如何用 jest 测试自定义 web 组件?

问题描述 投票:0回答:5

我想测试一些自定义 Web 组件并使用 jest.js 作为测试运行器(因为它支持 ES6)。

Chromium 支持像这样的命令

window.customElements.define('my-custom-element', MyCustomElementClass);

注册自定义 Web 组件。

然而,

window.customElements
在笑话测试的上下文中似乎并不为人所知。

作为解决方法,我尝试将 jest 与 puppeteer 和 express 结合使用,以在 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;           
         });

...我得到错误

'import' 和 'export' 只能出现在顶层

=> 使用 jest 测试自定义 Web 组件的推荐方法是什么?

一些相关的东西:

unit-testing jestjs puppeteer web-component
5个回答
6
投票

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.

注意并非所有功能都受支持,特别是shadow DOM 不工作.


4
投票

我创建了一个支持 Web 组件服务器端渲染的 DOM。它还支持使用 Jest 测试 Web 组件。

DOM:

https://www.npmjs.com/package/happy-dom

开玩笑的环境:

https://www.npmjs.com/package/jest-environment-happy-dom

安装它

npm 安装 jest-environment-happy-dom --save-dev

使用方法:

编辑您的 package.json 以包含 Jest 环境:

{
    "scripts": {
        "test": "jest --env=jest-environment-happy-dom"
    }
}

编辑:

包的名称已更改为“@happy-dom/jest-environent”


2
投票

使用 electron runner 可以包含所有节点和 chrome 环境, 用它来代替 jsdom

https://github.com/facebook-atom/jest-electron-runner


0
投票

这是一个丑陋的版本。关于此的一些进一步说明:

  • express.js 配置为 文件服务器。否则,导入的 ES6 模块的 MIME 类型或跨源检查会出现问题。

  • TreezElement
    不是直接导入的,而是使用创建额外脚本标签

  • 的变通方法
  • 关于代码覆盖率的实例方法存在问题。好像不能直接调用

    TreezElement
    的构造函数(继承自HTMLElement,=>
    illegal constructor
    )。元素类实例只能在puppeteer中用
    document.createElement(...)
    创建。因此,在 Jest 中不能测试所有实例方法,只能测试静态方法。可以在 puppeteer 中测试实例方法和属性。但是,jest的代码覆盖率没有考虑puppeteer的代码覆盖率

  • 创建的

    TreezElement
    类型的元素可以以ElementHandle的形式返回。 访问元素实例的属性和方法非常麻烦(见下面的例子)。作为手柄方法的替代方法,可以应用
    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();
    });    

});

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
属性:

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
的开头,但是 所有导入都在执行导入的脚本之前执行。这就是为什么我不得不将模拟代码放在一个额外的文件中。)

© www.soinside.com 2019 - 2024. All rights reserved.