Jsdom: Suporte para API de WebComponents

Criado em 17 fev. 2015  ·  89Comentários  ·  Fonte: jsdom/jsdom

Estamos tentando usar jsdom para nossos testes de unidade para o núcleo React (http://facebook.github.io/react/).

Infelizmente, a especificação de componentes da web não é suportada nativamente pelo jsdom, e o polyfill webcomponents.js não é executado no jsdom. Este problema solicita a adição de suporte a WebComponent (elementos personalizados, shadow dom, importações de html, etc).

feature

Comentários muito úteis

TL;DR

Trabalhei nas últimas 2 semanas avaliando a viabilidade de adicionar suporte para elemento personalizado em jsdom. Aqui está o resultado da investigação.

Você pode encontrar uma implementação compatível com especificações do elemento personalizado aqui: https://github.com/jsdom/jsdom/compare/master...pmdartus :custom-elements?expand=1. Ainda existem algumas arestas aqui e ali, mas pelo menos a maior parte do teste WPT passa . Os testes com falha restantes são problemas conhecidos do JSDOM ou problemas menores que podem ser resolvidos quando abordamos a implementação real no jsdom.

Agora, aqui estão as boas notícias, agora que o Shadow DOM é suportado nativamente, com o branch de elemento personalizado e o observador de mutação, consegui carregar e renderizar a versão mais recente do exemplo de aplicativo Polymer 3 hello world em jsdom 🎉. Em seu estado atual, a ramificação não pode carregar um aplicativo Stencil (o modo de desenvolvimento Stencil requer alguns recursos não suportados, como module , e o modo prod é lançado por um motivo desconhecido).

Plano de ação

Aqui está uma lista das mudanças que precisam acontecer antes, começando a lidar com a implementação real da especificação do elemento personalizado. Cada item da lista é independente e pode ser abordado em paralelo.

Suporte para atributos estendidos de [CEReactions] IDL

Um dos principais funcionalmente ausentes no jsdom para adicionar suporte a elementos personalizados são os atributos [CEReactions] . Eu fui parcialmente capaz de contornar esse problema corrigindo as propriedades corretas do protótipo. Essa abordagem funciona desde que a pilha de reação do elemento personalizado seja global e não por unidade de contextos de navegação de origem semelhante relacionados, pois todos os protótipos de interfaces são compartilhados.

Essa abordagem tem algumas deficiências, pois algumas interfaces possuem os atributos [CEReactions] associados a propriedades indexadas ( HTMLOptionsCollection , DOMStringMap ). Internamente, o jsdom usa Proxies para rastrear a mutação dessas propriedades. O patch de protótipo da interface não funciona neste caso. Outra abordagem, para contornar esse problema, seria corrigir a implementação em vez da interface (não implementada).

Não estou familiarizado o suficiente com o webidl2js interno, mas devemos explorar a adição de um gancho global para [CEReactions] ?

Mudanças:

Suporte para atributos estendidos de [HTMLConstructor] IDL

Como @domenic explicou acima , adicionar suporte para [HTMLConstructor] é um dos principais bloqueadores aqui.

Consegui contornar esse problema aqui corrigindo o construtor de interface para cada contexto de navegação. O construtor de interface seria capaz de acessar a janela certa e o objeto de documento mantendo o protótipo compartilhado. Essa abordagem também evita a sobrecarga de desempenho de reavaliar o protótipo da interface para cada novo contexto de navegação.

Não tenho certeza se é a melhor abordagem aqui, mas atende aos requisitos sem introduzir sobrecarga extra de desempenho.

Mudanças:

Torne o algoritmo de análise de fragmentos compatível com a especificação (#2522)

Conforme discutido aqui , a implementação do algoritmo de análise de fragmentos HTML usada em Element.innerHTML e Element.outerHTML está incorreta. O algoritmo de análise precisa ser refatorado, para que os retornos de chamada de reações de elementos personalizados sejam invocados corretamente.

Mudanças:

Melhorar a pesquisa de interface para criar algo de elemento

Um dos problemas que encontrei rapidamente foi a introdução de novas dependências circulares ao adicionar suporte para criação de elementos personalizados. Tanto o CustomElementRegistry quanto o algoritmo create element requerem acesso às interfaces Element, criando um pesadelo de dependências circulares.

A abordagem adotada na ramificação foi criar um InterfaceCache , que permitiria a pesquisa de interface por namespace e nome do elemento, mas também pelo nome da interface. Os módulos de interface são avaliados lentamente e armazenados em cache uma vez avaliados. Essa abordagem elimina as dependências circulares porque as interfaces não são mais necessárias no nível superior.

Essa é uma abordagem para resolver esse problema de longa data no jsdom, um desses problemas com essa abordagem é que talvez ela quebrasse a versão webpacked / browserified do jsdom (não testada).

Mudanças:

~Corrigir Element.isConnected para suportar Shadow DOM (https://github.com/jsdom/jsdom/pull/2424)~

Esse é um problema que surgiu com a introdução do shadow DOM , o isConnected retorna false se o elemento for parte de uma árvore de sombra. Um novo teste WPT precisa ser adicionado aqui, pois nenhum teste está verificando esse comportamento.

Mudanças:

Corrige o documento do nó HTMLTemplateElement.templateContents (#2426)

O conteúdo do modelo conforme definido na especificação tem um documento de nó diferente do próprio HTMLTemplateElement. jsdom não implementa esse comportamento hoje e o HTMLTemplateElement e o conteúdo do modelo compartilham o mesmo
nó do documento.

Mudanças:

  • HTMLTemplateElement-impl.js
  • htmltodom.js . Essa alteração também tem algum efeito downstream no analisador. Se o elemento de contexto for um HTMLTemplateElement, o algoritmo de análise de fragmento HTML deve selecionar o nó do documento do conteúdo do modelo e não do próprio elemento.

Adicione etapas de adoção ausentes ao HTMLTemplateElement (#2426)

O HTMLTemplateElement precisa executar algumas etapas específicas quando for adotado em outro documento. Até onde sei, a interface do solo tem uma etapa especial de adoção. A implementação do algoritmo de adoção de nó também precisaria ser atualizada para invocar esta etapa de adoção.

Mudanças:

Adicionar suporte para pesquisa isValue no serializador parse5

O algoritmo de serialização de fragmentos HTML , ao serializar um Elemento, procura o valor is associado ao elemento e o reflete como um atributo no conteúdo serializado. Seria interessante adicionar outro gancho no adaptador de árvore parse5, que pesquisaria o valor is associado a um elemento getIsValue(element: Element): void | string .

Uma abordagem alternativa (não implementada) seria adicionar alterar o comportamento do gancho getAttrList atual para retornar o valor is à lista de atributos, se o elemento tiver um valor is associado.

atuação

Antes de fazer qualquer otimização de desempenho, eu também queria verificar o desempenho das mudanças na filial. A adição de elementos personalizados adiciona uma sobrecarga de desempenho de 10% em comparação com o resultado atual no master para benchmarks de mutação de árvore. No entanto, a criação do novo ambiente JSDOM é agora 3x a 6x mais lenta em comparação com o mestre, exigiria uma investigação mais profunda para identificar a causa raiz.

Mais detalhes: aqui

Todos 89 comentários

Seria interessante verificar quais APIs o webcomponents.js usa e que o jsdom não suporta. Se eu tivesse que adivinhar, isso seria muito mais fácil de implementar do que a especificação completa dos componentes da web.

Dito isso, seria muito legal implementar componentes web. Provavelmente não é tão difícil quanto se poderia pensar --- as especificações são relativamente pequenas.

Só tive tempo de cavar um pouco nisto:

Primeiro, não temos Window definido no escopo da janela. Acabei de corrigir isso com this.Window = this.prototype no construtor Window .
Segundo, webcomponentsjs espera que Window tenha outro protótipo, ou seja, o protótipo EventTarget , que não implementamos como uma entidade separada.

Apenas um pouco de informação, porque eu tinha um pouco de tempo.

Agradável. Deve ser capaz de expor o Windows com bastante facilidade. O protótipo EventTarget é um pouco mais complicado, mas parece factível, dada a forma como atualmente implementamos essas coisas; tem sido um TODO meu.

Ok, os patches até agora são bastante fáceis:

  • [x] this.Window = Window; no construtor Window
  • [x] inherits(dom.EventTarget, Window, dom.EventTarget.prototype); após a definição de Window

A próxima falha do webcomponents.js acontece porque não implementamos HTMLUnknownElement (#1068), após corrigir que precisamos implementar o SVGUseElement . É nisso que estou bloqueado no momento, porque webcomponents.js aparentemente não gosta do SVGUseElement shimled por um HTMLDivElement e lança uma declaração.

Ok, eu verifiquei o Polyfill um pouco mais, precisamos implementar/você precisa corrigir o seguinte:

  • [x] HTMLUnknownElement #1068
  • [ ] SVGUseElement
  • [ ] window.CanvasRenderingContext2D
  • [ ] APIs de intervalo (incluindo: document.getRange() , window.getSelection() , window.Range , window.Selection ; #804 pode ser um começo)
  • [ ] npm i canvas

(lista não exaustiva por enquanto)

Um começo é algo como o seguinte:

jsdom.env({
  file: __dirname + '/index.htm', // refers to webcomponent.js
  created: function (err, window) {
    jsdom.getVirtualConsole(window).sendTo(console)

    window.document.createRange = function () { }
    window.getSelection = function () { }
    window.Range = function () { }
    window.Selection = function () { }
    window.CanvasRenderingContext2D = function () { } // Object.getPrototypeOf(require("canvas")(0,0).getContext("2d")) might be better
    window.SVGUseElement = window.HTMLUnknownElement
  },
  done: function (err, window) {
    console.log(err[0].data.error);
    console.log(window.CustomElements)
  },
  features: {
    ProcessExternalResources: ['script']
  }
});

Feito isso, há algum bug em nosso construtor HTMLDocument , que leva a um erro máximo de pilha de chamadas. O construtor no momento é apenas para uso interno, porém é válido que algum script do site faça chamadas para ele, então precisamos disponibilizar esse construtor para consumo público.

+1 Adoraria ver WebComponents no jsdom, principalmente porque o Polymer ganha popularidade, seria ótimo poder testar elementos personalizados em um sistema headless.

No momento, não há uma definição entre navegadores de componentes da Web, portanto, seria prematuro implementá-la. (Não vamos apenas copiar o Chrome.) Enquanto isso, você pode tentar usar o Polymer com jsdom.

@domenic justo o suficiente. Bem, é mais o suporte para o polyfill WebComponents.js que estou procurando, pois é disso que Polymer depende - ou webcomponents-lite (polyfills todos eles exceto Shadow DOM) no momento. Fiz algumas tentativas de fazer o Polymer funcionar no jsdom, mas sem sorte até agora - estou assumindo que as tarefas do @Sebmaster no comentário acima precisarão pelo menos ser corrigidas primeiro.

Meu entendimento é que existem três polyfills separados em questão. O do OP é separado do Polymer. Depois, há os polyfills webcomponents.org, que costumavam ser usados ​​no antigo Polymer. Então, no Polymer 1.0, eles têm seus próprios polyfills, eu acho, que não são realmente polyfills, mas bibliotecas alternativas que fazem coisas meio componentes da web. Talvez seja o webcomponents-lite.

No repositório WebComponentsJS, ele diz que o webcomponentsjs-lite é uma variante , fornecendo polyfills para todos os _but_ Shadow DOM, que o Polymer então tenta corrigir independentemente usando seu sistema Shady DOM. Então, a partir disso, tenho certeza de que o Polymer depende dos WebComponents o máximo que pode, com o polyfill WebComponentsJS fazendo o trabalho pesado. A versão lite deve ter um peso significativamente menor (curiosamente ..), então vou ver se consigo identificar o que é que o jsdom precisa para a versão lite. Quais você acha que são as chances de fazer o polyfill (lite ou full) funcionar no jsdom?

É realmente difícil dizer sem alguma investigação... ansioso pelo que você descobrirá.

Sim, acho que minha lista de tarefas ainda é aplicável e necessária para usar os calços. A mesclagem do #1227 pode nos tornar muito mais rápidos na implementação de interfaces compatíveis com os padrões, para que possamos corrigir os ausentes mais rapidamente.

Eu (provavelmente ingenuamente) comecei a trabalhar na adição de CustomElementsRegistry como uma maneira de entender como o jsdom é estruturado. Eu adicionei "custom-elements/custom-elements-registry/define.html" à lista de testes da plataforma web e ele passa quando não deveria (ainda não implementei o suficiente). Tenho certeza de que o teste não está realmente em execução, pois até mesmo adicionar um throw no topo do teste não impedirá que ele passe. Então eu obviamente perdi alguma coisa; além de adicionar o teste em test/web-platform-tests/index.js , há mais alguma coisa que eu precise fazer?

Parece que isso é causado porque falhamos na linha inicial const testWindow = iframe.contentDocument.defaultView; porque contentDocument é indefinido por algum motivo. Pode ser um problema com nossa ordem de carregamento vs. execução de script, mas não investiguei isso. Espero que ajude você a contornar isso. Talvez tenhamos que simplificar o teste para nossos propósitos (por enquanto).

Isso ajuda muito, obrigado! Vou ver se descubro o que está acontecendo lá, se não vou criar um teste simplificado como você recomendou.

@Sebmaster Caso você esteja interessado, pesquisei um pouco sobre o que está acontecendo com esse teste e os resultados são surpreendentes para mim.

O teste está usando o recurso de acesso nomeado de html . Isso significa que você pode fazer coisas como:

<div id="foo"></div>
<script>
  console.log(window.foo === document.getElementById('foo'));
</script>

_No entanto_, se o elemento tiver um contexto de navegação aninhado, o global deve apontar para isso (consulte a especificação vinculada). Para iframes, esse é o contentWindow . jsdom acerta, existe até um teste . Safari acerta também.

O que é louco é que o Chrome e o Firefox entendem isso errado; o global aponta para o iframe, não é contentWindow. Vendo isso, assumi que era um bug do jsdom e fiz algumas buscas, eventualmente encontrando esse teste, o que me levou à especificação.

tldr; trabalhar no jsdom é muito educativo e vocês fazem um trabalho incrível.

Indo para arquivar bugs nos respectivos navegadores. Também enviarei um PR para web-platform-tests, encontrei alguns outros erros no teste também.

Isso é ainda mais motivação para testes upstream como https://github.com/tmpvar/jsdom/blob/master/test/living-html/named-properties-window.js para WPT. Obrigado por postar! Isso me faz sentir muito bem com o jsdom ^_^

Oi!

Consegui fazer o polyfill de elementos personalizados funcionar com jsdom combinando

Nota: o repositório usa jsdom 8.5.0. A razão é que eu só tive sucesso com um polyfill MutationObserver, que usa Mutation Events internamente. Os eventos de mutação foram removidos após 8.5.0 devido ao mau desempenho. Se o Mutation Observer nativo for lançado, removerei o polyfill e atualizarei para o jsdom mais recente.

Eu tenho o jsdom mais recente e https://github.com/WebReflection/document-register-element está funcionando para mim! Eu tenho experimentado os polyfills mais oficiais e estou tendo problemas por algum motivo. Meu objetivo é fazer com que pelo menos elementos personalizados e importações de html funcionem... seria incrível se pudéssemos fazer o Polymer funcionar também.

Eu posso fazer com que os scripts do Polymer sejam executados sem erros. Posso até criar um componente e passá-lo para o construtor Polymer. Depois disso, ele falha silenciosamente. Acho que o shadow DOM é o problema.

Eu tenho tentado fazer com que o polyfill de importação de HTML do webcomponentsjs funcione. Eu posso executar o script e acredito que minhas importações HTML executam um xmlhttprequest, mas não parece que os scripts nas minhas importações são executados.

Quer compartilhar um exemplo @lastmjs? Atualmente, estou me aprofundando em componentes da web. Se eu puder ajudar, com prazer contribuirei com você.

@snuggs Obrigado! Dê-me um ou dois dias, estou no meio de algumas coisas urgentes no momento.

@snuggs Se conseguirmos que o polyfill webcomponents-lite funcione, devemos poder usar o Polymer. Shadow DOM parece ser o polyfill mais difícil de trabalhar até agora, e se usarmos webcomponents-lite não teremos que nos preocupar com isso por enquanto, porque teremos acesso a template , custom elements e HTML imports .

Posso fazer com que as importações de HTML funcionem com o polyfill webcomponents-lite . Eu estava tendo um comportamento estranho, então me deparei com isso: https://github.com/Polymer/polymer/issues/1535 Parece que as importações HTML só podem ser carregadas em um protocolo não-arquivo habilitado para cors. Então, criei um servidor http rápido no diretório raiz do meu projeto:

npm install -g http-server
http-server --cors

E aqui está o código básico com o qual tenho trabalhado:

const jsdom = require('jsdom');

const doc = jsdom.jsdom(`
    <!DOCTYPE html>

    <html>
        <head>
            <script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
            <link rel="import" href="http://localhost:8080/bower_components/polymer/polymer.html">
        </head>

        <body>
            <test-app></test-app>

            <dom-module id="test-app">
                <template>
                </template>

                <script>
                    setTimeout(() => {
                        class TestApp {
                            beforeRegister() {
                                this.is = 'test-app';
                                console.log('before register');
                            }

                            ready() {
                                console.log('ready');
                            }

                            created() {
                                console.log('created');
                            }

                            attached() {
                                console.log('attached');
                            }
                        }

                        Polymer(TestApp);
                    }, 1000);
                </script>
            </dom-module>
        </body>
    </html>
`, {
    virtualConsole: jsdom.createVirtualConsole().sendTo(console)
});

Por algum motivo, tenho que envolver a instanciação TestApp em um setTimeout . Parece que a importação do Polymer HTML não está bloqueando a renderização do resto do HTML, então sem o setTimeout o construtor Polymer não está definido. Esse é um comportamento normal para importações HTML?

beforeRegister é chamado, então o construtor Polymer está fazendo alguma coisa. Portanto, agora temos importações de HTML, é claro, modelos trabalhando com o polyfill webcomponents-lite . Não tenho certeza de como o polyfill de elementos personalizados está se saindo.

Quando eu coloco dentro da classe $ TestApp um método ready ou created , eles não são chamados. Parece que os eventos do ciclo de vida não estão sendo tratados adequadamente. A raiz desse problema pode estar na implementação dos elementos personalizados polyfill. Vou continuar brincando.

Possíveis problemas a serem resolvidos:

  • [ ] As importações HTML não estão bloqueando corretamente
  • [ ] elemento personalizado polyfill funcionando ou não?
  • [ ] Métodos de ciclo de vida do polímero não sendo chamados

Mais ajustes estão levando a mais insights. Acho que a ordem das importações e registros pode estar atrapalhando as coisas para nós. Quando executo const testApp = document.createElement('test-app'); depois que o construtor Polymer é chamado, os métodos created e ready são chamados, mas não o método attached . Talvez o jsdom não esteja lidando com literais de elementos personalizados corretamente? Além disso, mesmo ao chamar document.body.appendChild(testApp) , o método de ciclo de vida attached nunca é chamado.

Isso pode ajudar a entender a ordem de carregamento: https://github.com/webcomponents/webcomponentsjs#helper -utilities

@lastmjs atualmente estou lançando moedas entre CustomElementRegistry.define() e document.registerElement() . Eu vi que Domenic deu uma ótima contribuição e fundiu alguns trabalhos relativos ao whatwg (https://github.com/whatwg/html/issues/1329) Parece que há uma migração de API acontecendo. Por exemplo, acredito que a especificação chama connectedCallback que está emparelhada com a funcionalidade attachedCallback . Também supondo que você quis dizer attachedCallback quando disse attached , pois esse manipulador não faz parte da API. Eu experimentei define() e registerElement() disparando diferentes retornos de chamada respectivamente para cada método. Eu descobri a estratégia de elementos personalizados. HTMLImports Domenic mencionou antes de uma implementação que usa um patch XMLHTTPRequest. Acredito que possa converter diretamente para um DocumentFragment diretamente da resposta. Cheiros como esse podem ser óleo de cobra com as "importações". Uma importação "falsa" pode ser onde mora a sanidade.

Também parece haver algum fockery com super() sendo chamado em HTMLElement ao transpilar de ES6 -> ES5, então fique atento para isso. Eu tenho experimentado isso com Rollup.js/Babel e fui forçado a usar o (leve) shim do pacote webcomponents.
https://developers.google.com/web/fundamentals/getting-started/primers/customelements

Por fim, parece que obtenho (mais) sucesso quando crio com uma tag de protótipo.

document.createElement('main', 'test-app')

Como @domenic mencionou para mim antes, queremos ser cautelosos para implementar as especificações do menor denominador comum e não apenas fazer o que o GOOGLE faz. Parece que as linhas estão borradas com componentes da web. Mas sou fã.

Com quais métodos você tem trabalhado?

Até agora eu tenho jogado principalmente com os polyfills webcomponents-lite e Polymer < 2.0. Então, quando eu mencionei o método attached eu quis dizer o método do ciclo de vida Polymer que eles usam em vez do attachedCallback . Além disso, tanto quanto sei, os polyfills ainda não mudaram para a nova especificação de elementos personalizados v1. Então, tudo com que estou brincando é apenas na esperança de fazer com que o Polymer funcione com os polyfills atuais.

@snuggs Você está usando polyfills agora ou está trabalhando em uma implementação real em jsdom?

@lastmjs Não uso polyfills, pois sinto que não é necessário obter 80% do caminho. A plataforma está madura o suficiente agora que, com um pouco de ajuste inicial, pode usar apenas as construções nativas. Eu gosto de usar ferramentas leves (geralmente roladas à mão) em vez de estruturas. Dito isso, não é a maioria das pessoas. Parece que a intenção de Domenic é Custom Elements 👍 html imports 👎, mas não há problema em estender XMLHTTPRequest para lidar com o fetching do documento que nos levaria até lá. Isso foi há cerca de 6 meses. Muita coisa mudou desde a implementação. Muito possivelmente pensando. Então, onde terminamos @lastmjs?

@snuggs Talvez a coisa mais sensata e à prova de futuro a fazer seja implementar suporte de primeira classe para elementos personalizados e Shadow DOM no jsdom. Ambos os padrões estão na v1 e parece provável, pelo que estou ouvindo, que a maioria dos principais navegadores os implementará. Como devemos proceder? Eu tenho tempo limitado agora, mas talvez possamos definir o que precisa ser feito pelo menos. @domenic Você tem alguma sugestão sobre como avançar com essas implementações ou alguma razão pela qual não deveríamos?

Nenhuma sugestão concreta minha, além de apenas implementar a especificação :)

Eu tenho uma filial onde trabalhei nisso há algum tempo (a especificação mudou um pouco desde então). Implementar CustomElementsRegistry foi bastante fácil, onde eu lutei foi descobrir como tecer reações de elementos personalizados na base de código e quando eles deveriam ser chamados e de onde. Se eu fosse pegar isso de volta (sem promessas), provavelmente é nisso que eu me concentraria.

@matthewp Isso parece útil, onde posso encontrar esse ramo?

@matthewp sim, seria legal

https://github.com/matthewp/jsdom/commits/custom-elements como eu disse, a especificação mudou desde então, então está desatualizada. E esta é a parte mais fácil, mas é um ponto de partida se alguém quiser. @snuggs @lastmjs

(http://jonrimmer.github.io/are-we-componentized-yet/)

Pessoalmente, simplesmente apoiar o elemento Custom já seria ótimo.

(Observe que meu entendimento é que o phantomJS 2.5 deve suportar pelo menos Templates e talvez o elemento Custom, pois eles estão se movendo para a versão mais recente do Webkit, não tenho certeza de qual).

Na verdade, eu zombo do customElements, usando o lib document-register-element

const {before} = require('mocha')

before(mockDOM)
before(mockCustomElements)

function mockDOM() {
  const {JSDOM: Dom} = require('jsdom')
  const dom = new Dom('<!doctype html><html><body></body></html>')
  global.document = dom.window.document
  global.window = document.defaultView
  window.Object = Object
  window.Math = Math
}

function mockCustomElements() {
  require('document-register-element/pony')(window)
}

Maravilhoso, você teve algum problema?

até agora não :D

mas preciso escrever mais especificações, cobrir mais coisas para me sentir melhor

Incrível ver que existe um caminho. Por mais que eu goste de polímero, o test setu é um inferno e ter jsdom como fallback é bom ;) Obrigado por colocar o trabalho em

Parece que há um PR levando isso adiante! https://github.com/tmpvar/jsdom/pull/1872

Na verdade, eu zombo do customElements, usando o lib document-register-element @darlanmendonca

Deve ler este link sobre como anexar globais jsdom ao nó global. É um anti-padrão.

Olá a todos,
Estou um pouco confuso em relação ao status de execução do Polymer dentro do JSDOM (usando Node.js 6.7.0 e JSDOM 11.1.0). Eu tentei várias coisas, com resultados mistos. Agradeceria muito se alguém pudesse me informar aqui...

O que fiz até agora:

1) Eu acionei um servidor http do meu diretório raiz

./node_modules/http-server/bin/http-server --cors

2) Carreguei um dos meus componentes Polymer no JSDOM:

jsdom.JSDOM.fromURL("http://localhost:8080/path/to/my-component.html",
  { runScripts: "dangerously",
    resources: "usable"
  })
.then(function (dom) {
  setTimeout(() => {
    window = dom.window;
    component = window.document.querySelector("my-component");
  }, 10000);
})

(Também tentei carregar o arquivo de componente do sistema de arquivos, com os mesmos resultados.)

3) Este é o meu código de componente:

<!DOCTYPE html>
<html>
<head>
  <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
</head>

<body>
<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="order-app">
  <template>
    <h1>Hello Polymer</h1>
  </template>

  <script>
    console.log("javascript is being executed");
    addEventListener('WebComponentsReady', function () {
      console.log("web components are ready");
      Polymer({
        is: 'order-app'
      });
    });
  </script>
</dom-module>
</body>
</html>

(Adicionei o cabeçalho HTML para carregar o polyfill dos componentes da web.)

O que posso observar?

Quando eu executo isso, eu vejo

  • que o polyfill dos componentes da web está sendo carregado do servidor da web
  • a mensagem "javascript está sendo executado" no console

O que eu não vejo

  • que o componente polímero.html está sendo carregado do servidor web
  • a mensagem "componentes da web estão prontos" no console

Isso me leva à conclusão de que o evento WebComponentsReady não está sendo acionado (provavelmente porque a importação HTML não funciona?). Também,
window.WebComponents contém { flags: { log: {} } } -- o indicador ready está ausente.

Eu também tentei alguns mocking e polyfilling:

  window.Object = Object;
  window.Math = Math;
  require('document-register-element/pony')(window);

mas isso não parecia mudar nada.

Agora, eu estou querendo saber :-) Isso deveria funcionar? Se sim, por que não funciona para mim? Se não, o que está faltando/necessário para que funcione?

Obrigado por quaisquer insights!

Moin

Eu até tentei isso com ainda menos sucesso e desisti de esperar qual será o resultado dessa discussão aqui.

https://github.com/sebs/noframework/blob/master/test/configurator.js

não uma solução apenas mais uma tentativa fracassada. Mesma confusão tbm. Mesma conclusão também

O polyfilling de elementos personalizados no jsdom provou ser muito desafiador. Alguém pode listar os desafios de implementar isso no jsdom? Tentando avaliar o nível de esforço para conseguir isso.

O obstáculo fundamental é que o jsdom compartilha construtores e seus protótipos .

Isso torna basicamente impossível implementar um registro de elementos personalizados por janela, porque o construtor HTMLElement é compartilhado entre todas as janelas. Então, quando você faz a chamada super() em seu construtor de elemento personalizado, o construtor HTMLElement agora em execução não sabe em qual janela procurar as coisas. Isso é péssimo.

Não tenho certeza se existem boas soluções intermediárias. A grande arma é mover o jsdom para uma arquitetura que permita construtores/protótipos não compartilhados. Poderíamos fazer isso de algumas maneiras, todas com diferentes compensações. Talvez devêssemos abrir uma questão dedicada para discuti-la com a equipe e a comunidade, mas, por enquanto, deixe-me listar as que me vêm à mente de cabeça:

  • Use [WebIDL2JSFactory] para tudo em jsdom, ou pelo menos HTMLElement e todos os seus descendentes. Não tenho certeza se [WebIDL2JSFactory] ainda funciona bem com herança, mas pode ser feito para funcionar. Essa alternativa faz com que todos paguem o custo de construtores/protótipos extras, mas talvez isso seja melhor do que tornar os elementos personalizados um recurso opcional.
  • Tenha uma opção onde jsdom execute todos os módulos de definição de classe dentro do sandbox vm . Por exemplo, ter alguma etapa de compilação que empacota todas as APIs da Web em jsdom, então quando você cria uma nova janela, fazemos vm.runScript() com esse pacote dentro do novo sandbox global. Isso provavelmente nos permitiria nos livrar de [WebIDL2JSFactory] .

Acho que outra solução seria implementar elementos personalizados com um aviso gigante de que o registro de elementos personalizados é global por processo Node.js? Isso parece terrível embora.


Após esse obstáculo inicial, o resto é relativamente simples em termos de seguir as especificações. A parte mais difícil provavelmente será implementar [CEReactions] e atualizar todos os nossos arquivos IDL para ter isso nos lugares apropriados, mas não é muito difícil.

Eu tenho pensado em ter uma versão protótipo separada também. Aqui estão alguns dos meus pensamentos.

Não tenho certeza se [WebIDL2JSFactory] ainda funciona bem com herança, mas pode ser feito para funcionar.

Não, não, e não sei exatamente como fazê-lo funcionar. A segunda solução é muito mais direta na minha opinião.

Tenha uma opção onde jsdom execute todos os módulos de definição de classe dentro da sandbox vm .

Isto é o que eu preferiria. O principal problema é passar classes impl para a sandbox vm durante a inicialização, embora isso possa ser feito colocando tudo do contexto externo em uma propriedade global e delete nessa propriedade global depois de concluído . Também permitiria implementar corretamente [NamedConstructor] e alguns outros atributos estendidos, e talvez até gerar um instantâneo de inicialização V8 para um ambiente jsdom se alguém for ousado o suficiente.

Todo o negócio [WebIDL2JSFactory] foi um hack em primeiro lugar, e eu adoraria me livrar dele o mais rápido possível.

Comentários +1 não são úteis para desenvolver esse recurso, então estou excluindo pelo menos um recente.

Oi, eu não sabia que @TimothyGu estava trabalhando nisso.
Na verdade, tenho registro e criação de elementos personalizados trabalhando em
https://github.com/mraerino/jsdom/tree/custom-elements-spec

Estou tentando ser o mais minimamente invasivo possível e ficar o mais próximo possível das especificações.
Os testes da plataforma da Web do Custom Element Registry estão passando.

Enquanto hackeava ontem à noite, encontrei uma solução que funciona sem modificar o webIdl2JS.
Veja aqui: https://github.com/mraerino/jsdom/commit/592ad1236e9ca8f63f789d48e1887003305bc618

@TimothyGu você estaria disposto a combinar forças neste?

Apenas algumas atualizações aqui:
Estou bastante confiante sobre minha implementação da especificação, mas atualmente estou travado por causa do atributo IDL estendido [HTMLConstructor] . É por isso que abri https://github.com/jsdom/webidl2js/issues/87

Enquanto isso, implementarei o algoritmo [HTMLConstructor] usando um atributo [Constructor] para poder alternar facilmente mais tarde. (Inicialmente, implementei inserindo uma classe HTMLElement simulada em window , mas isso não parecia certo.)

Sim, conforme observado em https://github.com/tmpvar/jsdom/issues/1030#issuecomment -333994158, a implementação correta do HTMLConstructor exigirá alterações fundamentais na arquitetura do jsdom.

Você tem alguma informação sobre quantos testes de plataforma web sua versão está passando?

Apenas os customElementRegistry por enquanto, e posso estar totalmente errado sobre meu progresso.

Edit: Ok, depois de reler seu comentário, entendi o que você quis dizer. Vou tentar com minha implementação, mas @TimothyGu também parece estar trabalhando na separação.

Eu uso o Polymer, então estou :+1: neste recurso de solicitação

@dman777 @mraerino O mesmo para desenvolvedores slim.js. Slim usa API de componentes web nativos e não pode herdar HTMLElement sem hacks no jsdom.

Três anos se passaram desde que esta edição foi aberta. Alguém pode dizer quando aproximadamente jsdom suportará elementos personalizados?

TL;DR

Trabalhei nas últimas 2 semanas avaliando a viabilidade de adicionar suporte para elemento personalizado em jsdom. Aqui está o resultado da investigação.

Você pode encontrar uma implementação compatível com especificações do elemento personalizado aqui: https://github.com/jsdom/jsdom/compare/master...pmdartus :custom-elements?expand=1. Ainda existem algumas arestas aqui e ali, mas pelo menos a maior parte do teste WPT passa . Os testes com falha restantes são problemas conhecidos do JSDOM ou problemas menores que podem ser resolvidos quando abordamos a implementação real no jsdom.

Agora, aqui estão as boas notícias, agora que o Shadow DOM é suportado nativamente, com o branch de elemento personalizado e o observador de mutação, consegui carregar e renderizar a versão mais recente do exemplo de aplicativo Polymer 3 hello world em jsdom 🎉. Em seu estado atual, a ramificação não pode carregar um aplicativo Stencil (o modo de desenvolvimento Stencil requer alguns recursos não suportados, como module , e o modo prod é lançado por um motivo desconhecido).

Plano de ação

Aqui está uma lista das mudanças que precisam acontecer antes, começando a lidar com a implementação real da especificação do elemento personalizado. Cada item da lista é independente e pode ser abordado em paralelo.

Suporte para atributos estendidos de [CEReactions] IDL

Um dos principais funcionalmente ausentes no jsdom para adicionar suporte a elementos personalizados são os atributos [CEReactions] . Eu fui parcialmente capaz de contornar esse problema corrigindo as propriedades corretas do protótipo. Essa abordagem funciona desde que a pilha de reação do elemento personalizado seja global e não por unidade de contextos de navegação de origem semelhante relacionados, pois todos os protótipos de interfaces são compartilhados.

Essa abordagem tem algumas deficiências, pois algumas interfaces possuem os atributos [CEReactions] associados a propriedades indexadas ( HTMLOptionsCollection , DOMStringMap ). Internamente, o jsdom usa Proxies para rastrear a mutação dessas propriedades. O patch de protótipo da interface não funciona neste caso. Outra abordagem, para contornar esse problema, seria corrigir a implementação em vez da interface (não implementada).

Não estou familiarizado o suficiente com o webidl2js interno, mas devemos explorar a adição de um gancho global para [CEReactions] ?

Mudanças:

Suporte para atributos estendidos de [HTMLConstructor] IDL

Como @domenic explicou acima , adicionar suporte para [HTMLConstructor] é um dos principais bloqueadores aqui.

Consegui contornar esse problema aqui corrigindo o construtor de interface para cada contexto de navegação. O construtor de interface seria capaz de acessar a janela certa e o objeto de documento mantendo o protótipo compartilhado. Essa abordagem também evita a sobrecarga de desempenho de reavaliar o protótipo da interface para cada novo contexto de navegação.

Não tenho certeza se é a melhor abordagem aqui, mas atende aos requisitos sem introduzir sobrecarga extra de desempenho.

Mudanças:

Torne o algoritmo de análise de fragmentos compatível com a especificação (#2522)

Conforme discutido aqui , a implementação do algoritmo de análise de fragmentos HTML usada em Element.innerHTML e Element.outerHTML está incorreta. O algoritmo de análise precisa ser refatorado, para que os retornos de chamada de reações de elementos personalizados sejam invocados corretamente.

Mudanças:

Melhorar a pesquisa de interface para criar algo de elemento

Um dos problemas que encontrei rapidamente foi a introdução de novas dependências circulares ao adicionar suporte para criação de elementos personalizados. Tanto o CustomElementRegistry quanto o algoritmo create element requerem acesso às interfaces Element, criando um pesadelo de dependências circulares.

A abordagem adotada na ramificação foi criar um InterfaceCache , que permitiria a pesquisa de interface por namespace e nome do elemento, mas também pelo nome da interface. Os módulos de interface são avaliados lentamente e armazenados em cache uma vez avaliados. Essa abordagem elimina as dependências circulares porque as interfaces não são mais necessárias no nível superior.

Essa é uma abordagem para resolver esse problema de longa data no jsdom, um desses problemas com essa abordagem é que talvez ela quebrasse a versão webpacked / browserified do jsdom (não testada).

Mudanças:

~Corrigir Element.isConnected para suportar Shadow DOM (https://github.com/jsdom/jsdom/pull/2424)~

Esse é um problema que surgiu com a introdução do shadow DOM , o isConnected retorna false se o elemento for parte de uma árvore de sombra. Um novo teste WPT precisa ser adicionado aqui, pois nenhum teste está verificando esse comportamento.

Mudanças:

Corrige o documento do nó HTMLTemplateElement.templateContents (#2426)

O conteúdo do modelo conforme definido na especificação tem um documento de nó diferente do próprio HTMLTemplateElement. jsdom não implementa esse comportamento hoje e o HTMLTemplateElement e o conteúdo do modelo compartilham o mesmo
nó do documento.

Mudanças:

  • HTMLTemplateElement-impl.js
  • htmltodom.js . Essa alteração também tem algum efeito downstream no analisador. Se o elemento de contexto for um HTMLTemplateElement, o algoritmo de análise de fragmento HTML deve selecionar o nó do documento do conteúdo do modelo e não do próprio elemento.

Adicione etapas de adoção ausentes ao HTMLTemplateElement (#2426)

O HTMLTemplateElement precisa executar algumas etapas específicas quando for adotado em outro documento. Até onde sei, a interface do solo tem uma etapa especial de adoção. A implementação do algoritmo de adoção de nó também precisaria ser atualizada para invocar esta etapa de adoção.

Mudanças:

Adicionar suporte para pesquisa isValue no serializador parse5

O algoritmo de serialização de fragmentos HTML , ao serializar um Elemento, procura o valor is associado ao elemento e o reflete como um atributo no conteúdo serializado. Seria interessante adicionar outro gancho no adaptador de árvore parse5, que pesquisaria o valor is associado a um elemento getIsValue(element: Element): void | string .

Uma abordagem alternativa (não implementada) seria adicionar alterar o comportamento do gancho getAttrList atual para retornar o valor is à lista de atributos, se o elemento tiver um valor is associado.

atuação

Antes de fazer qualquer otimização de desempenho, eu também queria verificar o desempenho das mudanças na filial. A adição de elementos personalizados adiciona uma sobrecarga de desempenho de 10% em comparação com o resultado atual no master para benchmarks de mutação de árvore. No entanto, a criação do novo ambiente JSDOM é agora 3x a 6x mais lenta em comparação com o mestre, exigiria uma investigação mais profunda para identificar a causa raiz.

Mais detalhes: aqui

@pmdartus isso é muito promissor, excelente trabalho! Eu tenho usado meu hack branch jsdom-wc, por falta de uma opção melhor. Estou vendo um comportamento estranho e esperava trocar por sua ramificação, mas estou encontrando problemas.

Registo elementos personalizados como:

class Component extends HTMLElement {

}

customElements.define('custom-component', Component);

Mas se eu fizer:

const el = assign(this.fixture, {
  innerHTML: `
    <custom-component></custom-component>
  `,
});

Eu recebo um imediato: Error: Uncaught [TypeError: Illegal constructor] .

Quaisquer pensamentos sobre isso?

O seguinte trecho de código é executado corretamente no branch custom-elements no meu fork: https://github.com/pmdartus/jsdom/tree/custom-elements

const { JSDOM } = require("jsdom");

const dom = new JSDOM(`
<body>
  <div id="container"></div>
  <script>
    class Component extends HTMLElement {
        connectedCallback() {
            this.attachShadow({ mode: "open" });
            this.shadowRoot.innerHTML = "<p>Hello world</p>";
        }
    }
    customElements.define('custom-component', Component);

    const container = document.querySelector("#container");

    Object.assign(container, {
        innerHTML: "<custom-component></custom-component>"
    })

    console.log(container.innerHTML); // <custom-component></custom-component>
    console.log(container.firstChild.shadowRoot.innerHTML); // <p>Hello world</p>
  </script>
</body>
`, { 
    runScripts: "dangerously" 
});

O construtor ilegal provavelmente é lançado pelo construtor HTMLElement original, as alterações feitas na ramificação devem corrigir o construtor para cada novo objeto de janela. @tbranyen Você tem um exemplo de reprodução completo para que eu possa experimentá-lo localmente?

Oi @pmdartus Ainda não tenho certeza do que está causando meus problemas, mas escrevi um código isolado diretamente em sua ramificação que funcionou perfeitamente:

const { JSDOM } = require('.');
const window = (new JSDOM()).window;
const { HTMLElement, customElements, document } = window;

class CustomElement extends HTMLElement {
  constructor() {
    super();

    console.log('constructed');
  }

  connectedCallback() {
    console.log('connected');
  }
}

customElements.define('custom-element', CustomElement);
document.body.appendChild(new CustomElement());
//constructed
//connected

{
  const window = (new JSDOM()).window;
  const { HTMLElement, customElements, document } = window;

  class CustomElement extends HTMLElement {
    constructor() {
      super();

      console.log('Constructed');
    }

    connectedCallback() {
      console.log('Connected');
    }
  }

  customElements.define('custom-element', CustomElement);
  document.body.appendChild(new CustomElement());
  //constructed
  //connected
}

Isso é efetivamente o que meu sistema de teste faz, mas quebra. Então pode ser algo do meu lado.

Editar:

Ah ok, acho que reduzi onde o problema é mais provável. Devo estar segurando o construtor inicial HTMLElement criado. Se eu ajustar o código acima para reutilizar o construtor:

  // Inside the block, second component, reuse the HTMLElement.
  const { customElements, document } = window;

Isso produzirá o seguinte:

connected
/home/tbranyen/git/pmdartus/jsdom/lib/jsdom/living/helpers/create-element.js:643
        throw new TypeError("Illegal constructor");

Edição 2:

Encontrei:

  // Don't reuse the previously defined Element...
  global.HTMLElement = global.HTMLElement || jsdom.window.HTMLElement;

Percebendo que este tópico tem 4 anos, os componentes da Web são suportados ou planejados para ser?

Seria bom ter componentes da web nisso, mas como alternativa, se alguém quiser saber.... o chrome sem cabeça agora pode ser usado no nó para renderizar/compilar o arquivo html sting.

Percebendo que este tópico tem 4 anos, os componentes da Web são suportados ou planejados para ser?

É um trabalho em andamento, pois a especificação é implementada peça por peça.

O polyfill em: https://github.com/WebReflection/document-register-element Funciona como um encanto! Meus mais sinceros agradecimentos ao autor!

Para aqueles que lutam com o mesmo problema, basta fazer:

npm install -D document-register-element

Na configuração do seu jest, defina um arquivo de configuração que será executado antes de todos os seus testes:

{ "setupFilesAfterEnv": [ "./tests/setup.js" ] }

E, finalmente, dentro desse arquivo ('tests/setup.js'):

import 'document-register-element'

Depois de fazer isso, registrar e criar elementos personalizados no jsdom via document.createElement('custom-component') funciona perfeitamente! Fragmentos não parecem funcionar, no entanto. (A propósito, não estou usando shadow dom).

@tebanep como você mencionou que o polyfill é inadequado para a maioria dos trabalhos de Web Component, se ele não suporta Shadow DOM, então não é realmente uma comparação com o que isso está realizando.

@tebanep Obrigado. Como eu não preciso do shadow dom, essa é uma ótima informação

Alguma esperança de que isso seja implementado? Agora estamos usando jsdom-wc com muitos bugs, mas não temos nenhuma solução melhor. Minha esperança e rezo sobre este tema.

@dknight Eu sei que jsdom-wc é praticamente um hack para fazê-lo funcionar. Publiquei o módulo com compatibilidade significativamente melhor no meu escopo npm pessoal. Você pode instalá-lo com:

npm install @tbranyen/[email protected] --save-dev

Eu uso isso agora para todas as minhas necessidades de componentes web JSDOM até terras estáveis.

@tbranyen Você cancelou a publicação do seu fork? Não consigo encontrar no npm.

@KingHenne dangit, parece que acabou em nosso registro "empresa". Acabei de publicar ao público npm. Me desculpe por isso!

Não me @, mas não deveríamos apenas testar o código da interface do usuário da web em um navegador real, por exemplo, com o marionetista. O problema de suporte do Shadow DOM / Custom Elements desaparece então.

Não poste um comentário se você não quiser ser @'d @Georgegriff. Essa é uma estratégia válida, mas é lenta e cheia de bugs de outras maneiras, já que você está efetivamente fazendo IPC, sim, mesmo com o marionetista. Quando o navegador morre, não é óbvio o porquê em muitos casos. Apenas tente depurar os problemas do marionetista de brincadeira para ter uma ideia de por que nem sempre é a melhor ideia.

Pessoalmente, prefiro continuar testando síncrono e no mesmo thread. Não há razão para que uma implementação isolada da especificação não seja um tempo de execução razoável para testar componentes. O JSDOM é efetivamente um navegador neste momento, mas não tão estável quanto os três grandes.

O polyfill em: https://github.com/WebReflection/document-register-element Funciona como um encanto! Meus mais sinceros agradecimentos ao autor!

Para aqueles que lutam com o mesmo problema, basta fazer:

npm install -D document-register-element

Na configuração do seu jest, defina um arquivo de configuração que será executado antes de todos os seus testes:

{ "setupFilesAfterEnv": [ "./tests/setup.js" ] }

E, finalmente, dentro desse arquivo ('tests/setup.js'):

import 'document-register-element'

Depois de fazer isso, registrar e criar elementos personalizados no jsdom via document.createElement('custom-component') funciona perfeitamente! Fragmentos não parecem funcionar, no entanto. (A propósito, não estou usando shadow dom).

Funciona bem para mim, mas o connectedCallback nunca é chamado, alguma ideia?

@FaBeyyy mesmo para mim :(

@FaBeyyy @majo44 você precisa anexar seu componente a um documento, ou seja. document.body.appendChild(...) por connectedCallback para ser demitido. Por especificações, está sendo chamado quando o componente é anexado a um Dom.

O JSDOM é efetivamente um navegador neste momento, mas não tão estável quanto os três grandes.

Neste ponto, é mais como os dois grandes, porque a Microsoft está abandonando o deles, que está com eles há tanto tempo quanto o Windows.

@FaBeyyy @majo44 você precisa anexar seu componente a um documento, ou seja. document.body.appendChild(...) por connectedCallback para ser demitido. Por especificações, está sendo chamado quando o componente é anexado a um Dom.

obrigado capitão óbvio, mas é claro que não é o problema aqui. Se eu não soubesse como funciona o ciclo de vida do componente provavelmente não estaria tentando escrever testes 😄. Criarei uma vitrine de repositório mais tarde, quando encontrar tempo.

@FaBeyyy
Então eu encontrei a configuração que funciona para mim. Eu tive que adicionar polyfill para MutationObserver. Estou usando o JSDOM para testar o boto, com Jest, e a configuração de trabalho é:

// package.json
{  ...
  "jest": {
    "transform": {
      "^.+\\.(mjs|jsx|js)$": "babel-jest"
    },
    "setupFiles": [
      "<rootDir>/node_modules/babel-polyfill/dist/polyfill.js",
      "<rootDir>/node_modules/mutationobserver-shim/dist/mutationobserver.min.js",
      "<rootDir>/node_modules/document-register-element/build/document-register-element.node.js"
    ]
  }
... 
}
//.bablerc
{
    "presets": [
        ["@babel/preset-env", { "modules": "commonjs"}]
    ]
}

@FaBeyyy
Então eu encontrei a configuração que funciona para mim. Eu tive que adicionar polyfill para MutationObserver. Estou usando o JSDOM para testar o boto, com Jest, e a configuração de trabalho é:

// package.json
{  ...
  "jest": {
    "transform": {
      "^.+\\.(mjs|jsx|js)$": "babel-jest"
    },
    "setupFiles": [
      "<rootDir>/node_modules/babel-polyfill/dist/polyfill.js",
      "<rootDir>/node_modules/mutationobserver-shim/dist/mutationobserver.min.js",
      "<rootDir>/node_modules/document-register-element/build/document-register-element.node.js"
    ]
  }
... 
}

//.bablerc { "presets": [ ["@babel/preset-env", { "modules": "commonjs"}] ] }

Legal, obrigado!

@majo44 isso não é necessário com o jsdom mais recente. Ao trabalhar com Jest (que está usando jsdom v11), você pode usar apenas o ambiente atualizado: https://www.npmjs.com/package/jest-environment-jsdom-fourteen

@mgibas obrigado, com jest-environment-jsdom-fourteen também funciona bem e o observador de mutação polyfill não é necessário (mas a versão é 0.1.0, pacote de confirmação única :) )

Existe um detalhamento de quais APIs de componentes da Web são atualmente suportadas pelo JSDOM? Parece que o shadow DOM é suportado, mas não elementos personalizados (pelo menos no branch/repo de lançamento)?

npm install @tbranyen/[email protected] --save-dev

@tbranyen você tem o código-fonte do seu fork disponível em algum lugar? Seria curioso ver o diff 🙂

Estou usando jest-environment-jsdom-fifteen como @majo44 sugerido, e o babel-polyfill e document-register-element (veja as respostas do @mgibas ). Mas ainda recebo um erro quando tento recuperar meu shadow dom do componente da Web para testes.

el.shadowRoot é nulo com:

const el;
beforeEach(async() => {
  const tag= 'my-component'
  const myEl = document.createElement(tag);
  document.body.appendChild(myEl);
  await customElements.whenDefined(tag);
  await new Promise(resolve => requestAnimationFrame(() => resolve()));
  el = document.querySelector(tag);
}

it(() => {
  const fooContent = el.shadowRoot.querySelectorAll('slot[name=foo] > *');
})

Alguma idéia de uma solução alternativa? FYI, já foi testado com Karma, Mocha, Chai & Jasmine.

Nada de especial no meu componente:

customElements.define(
  'my-component',
  class extends HTMLElement {
    constructor() {
      super();

      const shadowRoot = this.attachShadow({ mode: 'open' });
      ...
  }
})

Editar

Fiz uma depuração com jsdom 15.1.1 para entender melhor meu problema.
Ainda assim, não entendo porque é nulo aqui ...

Então, Element.shadowRoot é implementado desde 88e72ef
https://github.com/jsdom/jsdom/blob/1951a19d8d40bc196cfda62a8dafa76ddda6a0d2/lib/jsdom/living/nodes/Element-impl.js#L388 -L415

Após document.createElement, this._shadowDom está ok em https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/living/nodes/Element-impl.js#L403. E cada elemento no shadow dom é criado (construtor de elemento chamado com os valores corretos).

Mas quando eu chamo el.shadowDom imediatamente após document.body.appendChild(el) (https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/living/nodes/Element-impl .js#L408), this. _shadowRoot é nulo!

Mesma coisa depois

 await customElements.whenDefined(tag);
 await new Promise(resolve => requestAnimationFrame(() => resolve()));

Ou mesmo se eu usar o seguinte em vez de document.

document.body.innerHTML = `
  <my-component id="fixture"></my-component>
`:

Para reprodução, consulte:
https://github.com/noelmace/devcards/tree/jest

@nminhnguyen Acho que você pode encontrar o código-fonte do fork feito por @tbranyan aqui https://github.com/tbranyen/jsdom/tree/initial-custom-elements-impl

Estou tentando testar componentes web feitos com lit-html e lit-element e notei essa diferença na hora de criar os elementos.

const myElem = new MyElem();

document.body.appendChild(myElem);
await myElem.updateComplete;

console.log(myElem.shadowRoot) // exists and has the rendered markup

e quando eu uso o document.createElement

const myElem = document.createElement('my-elem');

document.body.appendChild(myElem);
await myElem.updateComplete;

console.log(myElem.shadowRoot) // null

Para configurar o jest eu uso apenas um polyfill que é: setupFiles: ['document-register-element']

Parece que o método render em myElem nunca é chamado. Depurando um pouco mais, descobri que o método initialize que está no elemento lit nunca é chamado.
Então, o segundo exemplo funcionaria se eu fizer

const myElem = document.createElement('my-elem');
myElem.initialize();

document.body.appendChild(myElem);
await myElem.updateComplete;

console.log(myElem.shadowRoot) //  exists and has the rendered markup

Eu criei um DOM alternativo que suporta componentes da web. Primeiro tentei fazer um PR, mas a forma como o JSDOM funciona tornou difícil para mim resolver minhas necessidades lá. Você é livre para usá-lo ou olhar para o código.

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

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

Parece incrível. Obrigada.

Em segunda-feira, 7 de outubro de 2019, 01:08 capricorn86 [email protected] escreveu:

Eu criei um DOM alternativo que suporta componentes da web. eu primeiro
tentei fazer um PR, mas a forma como o JSDOM funciona dificultou minha resolução
precisa lá. Você é livre para usá-lo e/ou ver o código.

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

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


Você está recebendo isso porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/jsdom/jsdom/issues/1030?email_source=notifications&email_token=ACQ5ZD5QUEITPND4SXWOHW3QNILSRA5CNFSM4A4G5SF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOO5ZA#comment-53VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOO5ZA#comment-53ZLOORPWSZGOEAOO5ZA#comment-53 .
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/ACQ5ZDYU465DXI4KHBQH4KTQNILSRANCNFSM4A4G5SFQ
.

@capricorn86
Seu trabalho simplifica meu ambiente de teste, obrigado!
https://github.com/EasyWebApp/WebCell/tree/v2/MobX

@capricorn86
Seu trabalho simplifica meu ambiente de teste, obrigado!
https://github.com/EasyWebApp/WebCell/tree/v2/MobX

Obrigado @TechQuery!

Parece incrível. Obrigada.

Em segunda-feira, 7 de outubro de 2019, 1h08 capricorn86 @ . * > escreveu: Eu criei um DOM alternativo que suporta componentes web. Primeiro tentei fazer um PR, mas a forma como o JSDOM funciona tornou difícil para mim resolver minhas necessidades lá. Você é livre para usá-lo e/ou ver o código. DOM: https://www.npmjs.com/package/happy-dom Ambiente Jest: https://www.npmjs.com/package/jest-environment-happy-dom — Você está recebendo isso porque está inscrito neste fio. Responda a este e-mail diretamente, vê-lo no GitHub <# 1030? Email_source = notificações & email_token = ACQ5ZD5QUEITPND4SXWOHW3QNILSRA5CNFSM4A4G5SF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOO5ZA # issuecomment-538767076>, ou silenciar o fio https://github.com/notifications/unsubscribe-auth/ACQ5ZDYU465DXI4KHBQH4KTQNILSRANCNFSM4A4G5SFQ .

Obrigado @mots!

Existe um detalhamento de quais APIs de componentes da Web são atualmente suportadas pelo JSDOM? Parece que o shadow DOM é suportado, mas não elementos personalizados (pelo menos no branch/repo de lançamento)?

Também me interessaria isso :)

Esta página foi útil?
0 / 5 - 0 avaliações