feat(search): extended search, sort options, live tag filter, result count #183

Merged
marcel merged 26 commits from feat/issue-180-extended-search-sort into main 2026-04-06 19:18:12 +02:00
2 changed files with 81 additions and 0 deletions
Showing only changes of commit 2c0748d60e - Show all commits

View File

@@ -0,0 +1,69 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { debounce } from './debounce';
describe('debounce', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('does not fire before the delay has elapsed', () => {
const fn = vi.fn();
const debounced = debounce(fn, 200);
debounced();
vi.advanceTimersByTime(199);
expect(fn).not.toHaveBeenCalled();
});
it('fires exactly once after the delay', () => {
const fn = vi.fn();
const debounced = debounce(fn, 200);
debounced();
vi.advanceTimersByTime(200);
expect(fn).toHaveBeenCalledTimes(1);
});
it('resets the timer on each call — fires only once after inactivity', () => {
const fn = vi.fn();
const debounced = debounce(fn, 200);
debounced();
vi.advanceTimersByTime(100);
debounced();
vi.advanceTimersByTime(100);
debounced();
vi.advanceTimersByTime(200);
expect(fn).toHaveBeenCalledTimes(1);
});
it('passes the latest arguments to the callback', () => {
const fn = vi.fn();
const debounced = debounce(fn, 200);
debounced('first');
debounced('second');
vi.advanceTimersByTime(200);
expect(fn).toHaveBeenCalledWith('second');
});
it('can fire again after the first invocation settles', () => {
const fn = vi.fn();
const debounced = debounce(fn, 200);
debounced();
vi.advanceTimersByTime(200);
debounced();
vi.advanceTimersByTime(200);
expect(fn).toHaveBeenCalledTimes(2);
});
});

View File

@@ -0,0 +1,12 @@
/**
* Returns a debounced version of fn that delays invocation until after
* `delay` ms have elapsed since the last call.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {
let timer: ReturnType<typeof setTimeout>;
return ((...args: Parameters<T>) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
}) as T;
}