118 lines
4.1 KiB
TypeScript
118 lines
4.1 KiB
TypeScript
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 先完成(10ms),anim1 后完成(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);
|
||
});
|
||
});
|
||
});
|