c7644ade69
@robonen/vue (toolkit): migrate to eslint flat config + composite tsconfig; fix composable + test type errors (writable computed returns, null guards, overload-compatible signatures, typed test helpers) — all type-level.
204 lines
5.6 KiB
TypeScript
204 lines
5.6 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { effectScope, nextTick, ref } from 'vue';
|
|
import { watchIgnorable } from '.';
|
|
|
|
describe(watchIgnorable, () => {
|
|
it('fires the callback on normal updates (async flush)', async () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
watchIgnorable(count, cb);
|
|
|
|
count.value = 1;
|
|
await nextTick();
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenLastCalledWith(1, 0, expect.any(Function));
|
|
});
|
|
|
|
it('suppresses the callback inside ignoreUpdates (async flush)', async () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const { ignoreUpdates } = watchIgnorable(count, cb);
|
|
|
|
ignoreUpdates(() => {
|
|
count.value = 1;
|
|
});
|
|
await nextTick();
|
|
expect(cb).not.toHaveBeenCalled();
|
|
|
|
count.value = 2;
|
|
await nextTick();
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenLastCalledWith(2, 1, expect.any(Function));
|
|
});
|
|
|
|
it('commits when an ignored update is followed by a real change before flush (async)', async () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const { ignoreUpdates } = watchIgnorable(count, cb);
|
|
|
|
ignoreUpdates(() => {
|
|
count.value = 1;
|
|
});
|
|
// A real (non-ignored) change after the ignored one: syncCounter > ignoreCounter
|
|
count.value = 2;
|
|
await nextTick();
|
|
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenLastCalledWith(2, 0, expect.any(Function));
|
|
});
|
|
|
|
it('supports multiple chained ignored updates (async)', async () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const { ignoreUpdates } = watchIgnorable(count, cb);
|
|
|
|
ignoreUpdates(() => {
|
|
count.value = 1;
|
|
count.value = 2;
|
|
count.value = 3;
|
|
});
|
|
await nextTick();
|
|
expect(cb).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('fires the callback on normal updates (sync flush)', () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
watchIgnorable(count, cb, { flush: 'sync' });
|
|
|
|
count.value = 1;
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenLastCalledWith(1, 0, expect.any(Function));
|
|
});
|
|
|
|
it('suppresses the callback inside ignoreUpdates (sync flush)', () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const { ignoreUpdates } = watchIgnorable(count, cb, { flush: 'sync' });
|
|
|
|
ignoreUpdates(() => {
|
|
count.value = 1;
|
|
count.value = 2;
|
|
});
|
|
expect(cb).not.toHaveBeenCalled();
|
|
|
|
count.value = 3;
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenLastCalledWith(3, 2, expect.any(Function));
|
|
});
|
|
|
|
it('ignorePrevAsyncUpdates suppresses already-queued changes (async)', async () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const { ignorePrevAsyncUpdates } = watchIgnorable(count, cb);
|
|
|
|
count.value = 1;
|
|
// Drop the pending change before the async callback flushes
|
|
ignorePrevAsyncUpdates();
|
|
await nextTick();
|
|
expect(cb).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('ignorePrevAsyncUpdates only drops prior changes, not subsequent ones (async)', async () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const { ignorePrevAsyncUpdates } = watchIgnorable(count, cb);
|
|
|
|
count.value = 1;
|
|
ignorePrevAsyncUpdates();
|
|
count.value = 2;
|
|
await nextTick();
|
|
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenLastCalledWith(2, 0, expect.any(Function));
|
|
});
|
|
|
|
it('ignorePrevAsyncUpdates is a no-op for sync flush', () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const { ignorePrevAsyncUpdates } = watchIgnorable(count, cb, { flush: 'sync' });
|
|
|
|
count.value = 1;
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
// Calling after a sync change does nothing
|
|
ignorePrevAsyncUpdates();
|
|
count.value = 2;
|
|
expect(cb).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('stop tears down the watcher (async flush)', async () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const { stop } = watchIgnorable(count, cb);
|
|
|
|
stop();
|
|
count.value = 1;
|
|
await nextTick();
|
|
expect(cb).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('stop tears down the watcher (sync flush)', () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const { stop } = watchIgnorable(count, cb, { flush: 'sync' });
|
|
|
|
stop();
|
|
count.value = 1;
|
|
expect(cb).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('watches an array of sources', async () => {
|
|
const a = ref(0);
|
|
const b = ref('x');
|
|
const cb = vi.fn();
|
|
const { ignoreUpdates } = watchIgnorable([a, b], cb);
|
|
|
|
a.value = 1;
|
|
await nextTick();
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenLastCalledWith([1, 'x'], [0, 'x'], expect.any(Function));
|
|
|
|
ignoreUpdates(() => {
|
|
b.value = 'y';
|
|
});
|
|
await nextTick();
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('respects an eventFilter', async () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
// A filter that drops every invocation
|
|
watchIgnorable(count, cb, { eventFilter: () => {} });
|
|
|
|
count.value = 1;
|
|
await nextTick();
|
|
expect(cb).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('supports the immediate option', () => {
|
|
const count = ref(5);
|
|
const cb = vi.fn();
|
|
watchIgnorable(count, cb, { immediate: true, flush: 'sync' });
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
expect(cb).toHaveBeenLastCalledWith(5, undefined, expect.any(Function));
|
|
});
|
|
|
|
it('stops with the owning effect scope', async () => {
|
|
const count = ref(0);
|
|
const cb = vi.fn();
|
|
const scope = effectScope();
|
|
|
|
scope.run(() => watchIgnorable(count, cb));
|
|
|
|
count.value = 1;
|
|
await nextTick();
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
|
|
scope.stop();
|
|
count.value = 2;
|
|
await nextTick();
|
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|