You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Svelte组件动画触发代码的Vitest单元测试疑问

问题:Svelte动画触发的代码能否进行单元测试?

我创建了一个Svelte组件,用于快速显示后消失,告知用户操作已执行及影响的记录数量。简化后的组件代码如下:

<script lang="ts">
    import { type ShowStore } from "my-library";
    import { fade, blur } from 'svelte/transition';

    export let text: string;
    export let showStore: ShowStore;
    export let timeout = 500;

    function setExit() {
        console.log('setExit executed.');
        setTimeout(() => showStore.hide(), timeout);
    }
</script>

<div
    class="rounded-5 display-2 p-5 translate-middle fw-bold top-50 start-50"
    in:blur={{ duration: 200 }}
    out:fade
    on:introend={setExit}
>
    {text}
</div>

上述CSS类来自Bootstrap,用于将元素显示在屏幕中央。

我想用Vitest编写单元测试,验证组件是否会通过timeout属性指定的时长后自动隐藏,但测试未能正常运行:setExit函数内的console.log语句未执行,这让我怀疑测试环境中动画是否会触发——如果setExit未被调用,是不是意味着入场动画没有完成?

ShowStore是一个工具类Svelte store,存储布尔值,包含show()、hide()、toggle()方法,底层基于writable<boolean>()实现。

单元测试代码如下:

test('Should hide itself after the specified timeout value.', async () => {
    // Arrange.
    const text = '+123';
    const store = showStore();
    store.show();
    const timeout = 300;
    render(SwiftScore, { text, showStore: store, timeout });
    const spy = vi.spyOn(store, 'hide');

    // Act.
    await vi.waitFor(() => {
        console.log('Show value: %s', get(store));
        if (get(store)) {
            return Promise.reject();
        }
    }, timeout + 300);

    // Assert.
    expect(spy).toHaveBeenCalledOnce();
    expect(get(store)).toEqual(false);
});

当前使用的工具包括@testing-library/svelte、jsDom和Vitest。请问:由动画相关事件触发的代码能否进行单元测试?


解答

当然可以测试动画触发的代码,你的测试未运行是因为jsDom环境默认不会执行Svelte的过渡动画,也就不会触发introend事件,导致setExit从未被调用。以下是两种可行的解决思路:

1. 手动触发introend事件

既然测试环境中动画不会自动执行,直接获取组件元素,手动模拟触发introend事件即可跳过动画执行过程,聚焦逻辑验证:

test('Should hide itself after the specified timeout value.', async () => {
    // Arrange.
    const text = '+123';
    const store = showStore();
    store.show();
    const timeout = 300;
    const { container } = render(SwiftScore, { text, showStore: store, timeout });
    const spy = vi.spyOn(store, 'hide');

    // 手动触发introend事件,模拟入场动画完成
    container.firstElementChild.dispatchEvent(new Event('introend'));

    // Act.
    await vi.waitFor(() => {
        expect(get(store)).toBe(false);
    }, { timeout: timeout + 300 });

    // Assert.
    expect(spy).toHaveBeenCalledOnce();
});

2. 结合假计时器加速测试流程

使用Vitest的vi.useFakeTimers()可以快进时间,同时配合手动触发动画事件,既贴近真实逻辑又提升测试速度:

test('Should hide itself after the specified timeout value.', async () => {
    vi.useFakeTimers();
    
    // Arrange.
    const text = '+123';
    const store = showStore();
    store.show();
    const timeout = 300;
    const { container } = render(SwiftScore, { text, showStore: store, timeout });
    const spy = vi.spyOn(store, 'hide');

    // 触发introend事件,启动后续超时逻辑
    container.firstElementChild.dispatchEvent(new Event('introend'));
    // 快进时间至超时结束后
    vi.advanceTimersByTime(timeout + 10);
    await vi.runAllTimersAsync();

    // Assert.
    expect(spy).toHaveBeenCalledOnce();
    expect(get(store)).toBe(false);

    vi.useRealTimers();
});

关键说明

  • Svelte的过渡事件(如introend、outroend)在jsDom中不会自动触发,因为jsDom不处理CSS动画或Svelte的JavaScript过渡逻辑。
  • 单元测试的核心是验证业务逻辑正确性,而非动画效果本身,因此手动触发事件是完全合理的测试手段,无需在测试环境中完整运行动画。

内容的提问来源于stack exchange,提问作者José Ramírez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 19:24:50