我在使用 jest 和 vue test utlis 测试转换函数时遇到问题。这是我的组件代码:
<div class="disclosure">
<button
class="label"
type="button"
@click="toggle"
>
<span class="label-text">
<slot name="disclosure-title" />
</span>
</button>
<Transition
name="disclosure"
@before-enter="transitionBaseState"
@enter="transitionEndState"
@before-leave="transitionEndState"
@leave="transitionBaseState"
>
<div
v-show="open"
class="panel"
>
<div class="panel-content">
<slot />
</div>
</div>
</Transition>
</div>
我对转换函数有疑问:transitionBaseState 和transitionEndState。当我为组件运行 ejst spec 时,覆盖选项卡显示这些功能未被覆盖。您知道测试这些功能的最佳方法是什么吗?我对显示元素的测试是这样的:
it('can be changed to opened by clicking the panel', async () => {
await wrapper.find(buttonSelector).trigger('click');
expect(wrapper.find(panelSelector).isVisible()).toBe(true);
});
功能:
function transitionBaseState(el: HTMLElement): void {
el.style.height = '0';
}
function transitionEndState(el: HTMLElement): void {
el.style.height = `${el.scrollHeight}px`;
}
好的。这是最难的。 Vue 测试 utils 默认存根转换。并且文档中没有涵盖这种行为。您需要通过硬编码的
false
值来全局模拟它。
import { config, mount } from '@vue/test-utils'
...
// before Mount
config.global.stubs = {
transition: false
}
jest.useFakeTimers() // to deal with durations
// mount
// trigger click
jest.advanceTimersByTime(duration + 1)
// expect whatever you need from before-enter, enter, after-enter callbacks
// trigger click
jest.advanceTimersByTime(duration + 1)
// expect whatever you need from before-leave, leave, after-leave callbacks
如您所见,我添加了使用假计时器的解决方法来处理过渡持续时间。
事实上,在大多数情况下,您不需要测试转换的作用。 vue 核心团队已经对此进行了测试,但也许在某些情况下应该对其进行测试。正如您所看到的,测试中的行数增加只是为了避免默认行为。它还使用未记录的模拟,您应该在每次更新 Vue/vue-test-utils 并进行一些重大更改后保持其正常工作。
无论如何,我已经测试了上面的代码。这是我的尝试
it('should call all the callbacks', async () => {
expect.hasAssertions()
const callbacks = {
onBeforeEnter: jest.fn(),
onEnter: jest.fn(),
onAfterEnter: jest.fn(),
onBeforeLeave: jest.fn(),
onLeave: jest.fn(),
onAfterLeave: jest.fn(),
}
const component = defineComponent({
name: 'Component',
data() {
return {
flag: false
}
},
template: `<div>
<transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave">
<span v-if="flag"></span>
</transition>
</div>`,
methods: {
onBeforeEnter: callbacks.onBeforeEnter,
onEnter(e: Event, done: Function) {
callbacks.onEnter()
done()
},
onAfterEnter() {
callbacks.onAfterEnter()
},
onBeforeLeave() {
callbacks.onBeforeLeave()
},
onLeave(e: Event, done: Function) {
callbacks.onLeave();
done()
},
onAfterLeave() {
callbacks.onAfterLeave()
}
}
})
config.global.stubs = {
transition: false
}
jest.useFakeTimers()
const wrapper = mount(component)
await wrapper.setData({ flag: true })
expect(callbacks.onBeforeEnter).toHaveBeenCalled()
expect(callbacks.onEnter).toHaveBeenCalled()
expect(callbacks.onAfterEnter).toHaveBeenCalled()
await wrapper.setData({ flag: false })
jest.advanceTimersByTime(1)
expect(callbacks.onBeforeLeave).toHaveBeenCalled()
expect(callbacks.onLeave).toHaveBeenCalled()
expect(callbacks.onAfterLeave).toHaveBeenCalled()
})
这是我的解决方案:
const FakeTranstion = defineComponent({
emits: ['afterEnter'],
template: '<div><slot /></div>',
setup(_, { emit }) {
return {
emitAfterEnter() {
emit('afterEnter')
},
}
},
})
const subject = mount(YourComponent, {
propsData: {
modelValue,
},
global: {
stubs: {
transition: FakeTranstion,
},
},
})
subject.findComponent(FakeTranstion).vm.emitAfterEnter()