使用 Jest 运行测试时,为啥 Trix 编辑器不会安装在 Vue 组件中?

Posted

技术标签:

【中文标题】使用 Jest 运行测试时,为啥 Trix 编辑器不会安装在 Vue 组件中?【英文标题】:Why won't Trix editor mount in Vue component when running tests with Jest?使用 Jest 运行测试时,为什么 Trix 编辑器不会安装在 Vue 组件中? 【发布时间】:2019-09-18 07:19:44 【问题描述】:

我构建了一个简单的 Vue 组件来包装 Trix 编辑器。我正在尝试为其编写测试,但 Trix 似乎没有正确安装,并且没有像在浏览器中那样生成工具栏元素。我正在使用 Jest 测试运行器。

TrixEdit.vue

<template>
  <div ref="trix">
    <trix-editor></trix-editor>
  </div>
</template>

<script>
import 'trix'

export default 
  mounted() 
    let el = this.$refs.trix.getElementsByTagName('trix-editor')[0]
    // HACK: change the URL field in the link dialog to allow non-urls
    let toolbar = this.$refs.trix.getElementsByTagName('trix-toolbar')[0]
    toolbar.querySelector('[type=url]').type = 'text'

    // insert content
    el.editor.inserthtml(this.value)

    el.addEventListener('trix-change', e => 
      this.$emit('input', e.target.innerHTML)
    )
  

</script>

TrixEdit.spec.js

import  mount, shallowMount, createLocalVue  from '@vue/test-utils'
import TrixEdit from '@/components/TrixEdit.vue'

const localVue = createLocalVue()
localVue.config.ignoredElements = ['trix-editor']

describe('TrixEdit', () => 
  describe('value prop', () => 
    it('renders text when value is set', () => 
      const wrapper = mount(TrixEdit, 
        localVue,
        propsData: 
          value: 'This is a test'
        
      )

      expect(wrapper.emitted().input).toEqual('This is a test')
    )
  )
)

expect() 失败并出现以下错误

    Expected value to equal:
      "This is a test"
    Received:
      undefined

    at Object.toEqual (tests/unit/TrixEdit.spec.js:19:39)

为什么 Trix 在我的测试中没有初始化?

【问题讨论】:

你能分享有问题的回购吗? @hackape 不幸的是它是私人的。 mocha 单元测试在 nodejs 运行时运行,而 trix 仅在浏览器运行时运行。这就是它无法在单元测试中初始化的原因。 @hackape 这是开玩笑,不是摩卡。 好吧,mocha 还是 jest,问题都一样,运行时的区别 【参考方案1】:

trix-editor 没有挂载主要是因为MutationObserver 在 JSDOM 11 中不受支持,并且attachToDocument 没有被使用。下面描述的测试中还有其他几个错误。

GitHub demo w/issues fixed


缺少MutationObserverwindow.getSelection

Vue CLI 3.7.0 使用 JSDOM 11,它不支持 MutationObserver,自定义元素 polyfill 需要它来触发 connectedCallback。该生命周期挂钩通常会调用 trix-editor 的初始化,这将创建您的测试尝试查询的 trix-toolbar 元素。

解决方案 1: 在您的测试中,导入 mutationobserver-shim before TrixEdit.vue 和存根 window.getSelection(由 trix 调用,目前 JSDOM 不支持):

import 'mutationobserver-shim' // <-- order important
import TrixEdit from '@/components/TrixEdit.vue'

window.getSelection = () => ()

解决方案 2: 在由 setupTestFrameworkScriptFile 配置的 Jest 设置脚本中执行上述操作:

    将以下属性添加到jest.config.js(或package.json 中的jest)中的配置对象:
setupTestFrameworkScriptFile: '<rootDir>/jest-setup.js',
    将以下代码添加到&lt;rootDir&gt;/jest-setup.js
import 'mutationobserver-shim'
window.getSelection = () => ()

缺少attachToDocument

@vue/test-utils 默认情况下不会将元素附加到文档,因此trix-editor 不会捕获其初始化所需的connectedCallback

解决方法:安装TrixEdit时使用attachToDocument option:

const wrapper = mount(TrixEdit, 
  //...
  attachToDocument: true, // <-- needed for trix-editor
)

过早引用trix-editor的编辑器

TrixEdit 错误地假定trix-editor 在挂载时立即初始化,但在触发trix-initialize 事件之前不能保证初始化,因此访问trix-editor 的内部编辑器可能会导致undefined 引用.

解决方案:为调用之前在mounted()中的初始化代码的trix-initialize事件添加event handler:

<template>
  <trix-editor @trix-initialize="onInit" />
</template>

<script>
export default 
  methods: 
    onInit(e) 
      /* initialization code */
    
  

</script>

更改侦听器之前设置的值

初始化代码添加了一个trix-change-event 监听器值已经设置之后,缺少事件触发器。我假设其目的还在于检测第一个初始值设置,以便将其作为input 事件重新发出。

解决方案一:先添加事件监听器:

<script>
export default 
  methods: 
    onInit(e) 
      //...
      el.addEventListener('trix-change', /*...*/)

      /* set editor value */
    
  

</script>

解决方案2:在模板中使用v-on:trix-change="..."(或@trix-change),这样可以消除上面的设置顺序问题:

<template>
  <trix-editor @trix-change="onChange" />
</template>

<script>
export default 
  methods: 
    onChange(e) 
      this.$emit('input', e.target.innerHTML)
    ,
    onInit(e) 
      //...
      /* set editor value */
    
  

</script>

值设置导致错误

初始化代码用如下代码设置编辑器的值,导致测试出错:

el.editor.insertHTML(this.value) // causes `document.createRange is not a function`

解决方案:使用trix-editorvalue-accessor,它执行等效操作同时避免此错误:

el.value = this.value

【讨论】:

【参考方案2】:

我可以确认问题在于 trix lib 中包含的不太理想的聚合物填充物。我做了一个实验来强制应用 polyfill,然后我可以在 chrome 浏览器环境中重现相同的错误TypeError: Cannot read property 'querySelector' of undefined

进一步调查将范围缩小到 MutationObserver 行为差异,但仍未深入调查。

复制方式:

TrixEdit.vue

<template>
  <div ref="trix">
    <trix-editor></trix-editor>
  </div>
</template>

<script>

// force apply polymer polyfill
delete window.customElements;
document.registerElement = undefined;

import "trix";

//...

</script>

【讨论】:

相当恶心,但我会在星期一测试它,如果一切正常,我会奖励赏金。 值得注意的是,有一个 trix-initialize 事件可能在 mount 上起作用,而不需要 setTimeout 是的,这很恶心......但我没有兴趣再看下去,需要查看太多源代码 @Soviut 但是让我做一个有根据的猜测,差异可能源于@vue/test-utilsmounted 真实应用程序中的生命周期钩子可能是异步的。但是从const wrapper = mount()在测试中的使用方式来看,貌似hook是同步调用的。 @Soviut 我不禁深入挖掘......这是我迄今为止得到的最新结果。我的猜测是错误的。问题来自trix lib。它使用 WebComponent window.customElements API。在 jsdom 环境中,缺少 API,因此应用了聚合物 polyfill,但它似乎仍然不起作用。

以上是关于使用 Jest 运行测试时,为啥 Trix 编辑器不会安装在 Vue 组件中?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Jest 在测试“Colyseus”游戏时会出现这个错误?

为啥 Jest --runInBand 会加快测试速度?

在 Visual Studio Code 编辑器中完全禁用 Jest 测试运行器的自动运行

为啥 jest 寻找不再存在的测试文件?

为啥 Jest 不让我使用节点测试环境,即使他们自己更改了它?

为啥在运行玩笑测试时出现“未定义:x:y:属性”缺失错误?