boardgame-core/tests/utils/mutable-signal.test.ts

118 lines
4.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect } from 'vitest';
import { MutableSignal, mutableSignal } from '@/utils/mutable-signal';
describe('MutableSignal', () => {
it('should create signal with initial value', () => {
const s = mutableSignal({ count: 1 });
expect(s.value.count).toBe(1);
});
it('should produce immutable updates', () => {
const s = mutableSignal({ count: 1, items: [1, 2, 3] });
s.produce(draft => {
draft.count = 2;
draft.items.push(4);
});
expect(s.value.count).toBe(2);
expect(s.value.items).toEqual([1, 2, 3, 4]);
});
describe('interruption / produceAsync', () => {
it('should update immediately when no interruptions', async () => {
const s = mutableSignal({ value: 0 });
await s.produceAsync(draft => { draft.value = 1; });
expect(s.value.value).toBe(1);
});
it('should wait for addInterruption before updating', async () => {
const s = mutableSignal({ value: 0 });
let animationDone = false;
const animation = new Promise<void>(resolve => {
setTimeout(() => {
animationDone = true;
resolve();
}, 10);
});
s.addInterruption(animation);
const producePromise = s.produceAsync(draft => { draft.value = 42; });
// produceAsync 应该等待 animation 完成
expect(animationDone).toBe(false);
expect(s.value.value).toBe(0);
await producePromise;
expect(animationDone).toBe(true);
expect(s.value.value).toBe(42);
});
it('should wait for all interruptions in parallel', async () => {
const s = mutableSignal({ value: 0 });
const order: string[] = [];
const anim1 = new Promise<void>(resolve => {
setTimeout(() => { order.push('anim1'); resolve(); }, 20);
});
const anim2 = new Promise<void>(resolve => {
setTimeout(() => { order.push('anim2'); resolve(); }, 10);
});
s.addInterruption(anim1);
s.addInterruption(anim2);
await s.produceAsync(draft => { draft.value = 1; });
// anim2 先完成10msanim1 后完成20ms
expect(order).toEqual(['anim2', 'anim1']);
expect(s.value.value).toBe(1);
});
it('should not throw when an interruption rejects', async () => {
const s = mutableSignal({ value: 0 });
const failingAnim = Promise.reject<void>(new Error('animation cancelled'));
// 避免未捕获的 rejection 警告
failingAnim.catch(() => {});
s.addInterruption(failingAnim);
// allSettled 应该让 produceAsync 继续执行
await s.produceAsync(draft => { draft.value = 99; });
expect(s.value.value).toBe(99);
});
it('should clear interruptions after produceAsync resolves', async () => {
const s = mutableSignal({ value: 0 });
s.addInterruption(Promise.resolve());
await s.produceAsync(draft => { draft.value = 1; });
expect(s.value.value).toBe(1);
// 第二次 produceAsync 不应该再等待
await s.produceAsync(draft => { draft.value = 2; });
expect(s.value.value).toBe(2);
});
it('should clear all pending interruptions manually', async () => {
const s = mutableSignal({ value: 0 });
let animationDone = false;
const longAnim = new Promise<void>(resolve => {
setTimeout(() => {
animationDone = true;
resolve();
}, 100);
});
s.addInterruption(longAnim);
s.clearInterruptions();
await s.produceAsync(draft => { draft.value = 1; });
expect(s.value.value).toBe(1);
// clearInterruptions 后longAnim 仍在后台运行,但 produceAsync 不会等它
expect(animationDone).toBe(false);
});
});
});