fix(vue): eslint/tsconfig migration + resolve type errors

@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.
This commit is contained in:
2026-06-07 16:29:39 +07:00
parent e6919de29e
commit c7644ade69
203 changed files with 23016 additions and 141 deletions
@@ -0,0 +1,138 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { effectScope, nextTick, ref, watch } from 'vue';
import { refAutoReset } from '.';
describe(refAutoReset, () => {
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
it('initializes with the default value', () => {
const value = refAutoReset('default', 1000);
expect(value.value).toBe('default');
});
it('resets to the default value after the delay', () => {
const value = refAutoReset('default', 1000);
value.value = 'changed';
expect(value.value).toBe('changed');
vi.advanceTimersByTime(999);
expect(value.value).toBe('changed');
vi.advanceTimersByTime(1);
expect(value.value).toBe('default');
});
it('uses a default delay of 10000ms', () => {
const value = refAutoReset('default');
value.value = 'changed';
vi.advanceTimersByTime(9999);
expect(value.value).toBe('changed');
vi.advanceTimersByTime(1);
expect(value.value).toBe('default');
});
it('restarts the timer on each set', () => {
const value = refAutoReset('default', 1000);
value.value = 'first';
vi.advanceTimersByTime(800);
value.value = 'second';
vi.advanceTimersByTime(800);
// 1600ms total elapsed but only 800ms since last set
expect(value.value).toBe('second');
vi.advanceTimersByTime(200);
expect(value.value).toBe('default');
});
it('does not reset before any write', () => {
const value = refAutoReset('default', 1000);
vi.advanceTimersByTime(5000);
expect(value.value).toBe('default');
});
it('resolves a reactive default value at reset time', () => {
const fallback = ref('a');
const value = refAutoReset(fallback, 1000);
expect(value.value).toBe('a');
value.value = 'changed';
fallback.value = 'b';
vi.advanceTimersByTime(1000);
expect(value.value).toBe('b');
});
it('resolves a reactive delay on each set', () => {
const delay = ref(1000);
const value = refAutoReset('default', delay);
value.value = 'first';
delay.value = 500;
// delay is resolved at set time -> still 1000 for the first write
vi.advanceTimersByTime(1000);
expect(value.value).toBe('default');
// next set picks up the new delay
value.value = 'second';
vi.advanceTimersByTime(500);
expect(value.value).toBe('default');
});
it('resolves a getter as the default value', () => {
let base = 1;
const value = refAutoReset(() => base, 1000);
expect(value.value).toBe(1);
value.value = 99;
base = 5;
vi.advanceTimersByTime(1000);
expect(value.value).toBe(5);
});
it('is reactive and triggers watchers on set and on reset', async () => {
const scope = effectScope();
const spy = vi.fn();
scope.run(() => {
const value = refAutoReset('default', 1000);
watch(value, spy, { flush: 'sync' });
value.value = 'changed';
});
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenLastCalledWith('changed', 'default', expect.anything());
vi.advanceTimersByTime(1000);
await nextTick();
expect(spy).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenLastCalledWith('default', 'changed', expect.anything());
scope.stop();
});
it('cancels the pending reset when the owning scope is disposed', () => {
const scope = effectScope();
let value!: ReturnType<typeof refAutoReset<string>>;
scope.run(() => {
value = refAutoReset('default', 1000);
value.value = 'changed';
});
scope.stop();
vi.advanceTimersByTime(1000);
expect(value.value).toBe('changed');
});
});
@@ -0,0 +1,59 @@
import { customRef, toValue } from 'vue';
import type { MaybeRefOrGetter, Ref } from 'vue';
import { useTimeoutFn } from '@/composables/utilities/useTimeoutFn';
export type RefAutoResetReturn<T> = Ref<T>;
/**
* @name refAutoReset
* @category Reactivity
* @description Create a ref that resets to its default value after a delay
* since the last write. Each set restarts the timer; reading is reactive.
*
* @param {MaybeRefOrGetter<T>} defaultValue The value the ref resets to (resolved each reset, can be reactive)
* @param {MaybeRefOrGetter<number>} [afterMs=10000] Delay in milliseconds before resetting (resolved on each set, can be reactive)
* @returns {RefAutoResetReturn<T>} A ref that auto-resets to `defaultValue`
*
* @example
* const message = refAutoReset('', 1000);
* message.value = 'Saved!'; // reverts to '' after 1000ms
*
* @example
* // Reactive delay and default
* const delay = ref(2000);
* const fallback = ref('idle');
* const status = refAutoReset(fallback, delay);
*
* @since 0.0.15
*/
export function refAutoReset<T>(
defaultValue: MaybeRefOrGetter<T>,
afterMs: MaybeRefOrGetter<number> = 10000,
): RefAutoResetReturn<T> {
return customRef<T>((track, trigger) => {
let value: T = toValue(defaultValue);
const { start, stop } = useTimeoutFn(
() => {
value = toValue(defaultValue);
trigger();
},
afterMs,
{ immediate: false },
);
return {
get() {
track();
return value;
},
set(newValue) {
value = newValue;
trigger();
stop();
start();
},
};
});
}