feat: implement P0/P1 performance and code quality optimizations
P0 Performance Optimizations: - Add debounced search (300ms) to reduce re-renders during typing - Implement single-pass filter function for O(n) complexity - Add TanStack Query cancellation before new requests P1 Code Quality Optimizations: - Add runtime type guards for filter validation - Implement two derived values (filteredFonts + sortedFilteredFonts) - Remove all 'as any[]' casts from filter bridge - Add fast-path for default sorting (skip unnecessary operations) New Utilities: - debounce utility with 4 tests (all pass) - filterUtils with 15 tests (all pass) - typeGuards with 20 tests (all pass) - Total: 39 new tests Modified Files: - unifiedFontStore.svelte.ts: Add debouncing, use filter/sort utilities - filterBridge.svelte.ts: Type-safe validation with type guards - unifiedFontStore.test.ts: Fix pre-existing bugs (missing async, duplicate imports) Code Quality: - 0 linting warnings/errors (oxlint) - FSD compliant architecture (entity lib layer) - Backward compatible store API
This commit is contained in:
77
src/shared/lib/utils/debounce/debounce.test.ts
Normal file
77
src/shared/lib/utils/debounce/debounce.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
import { debounce } from './debounce';
|
||||
|
||||
describe('debounce', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should delay execution by the specified wait time', () => {
|
||||
const mockFn = vi.fn();
|
||||
const debounced = debounce(mockFn, 300);
|
||||
|
||||
debounced('arg1', 'arg2');
|
||||
|
||||
expect(mockFn).not.toHaveBeenCalled();
|
||||
|
||||
vi.advanceTimersByTime(300);
|
||||
|
||||
expect(mockFn).toHaveBeenCalledTimes(1);
|
||||
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
|
||||
});
|
||||
|
||||
it('should cancel previous invocation and restart timer on subsequent calls', () => {
|
||||
const mockFn = vi.fn();
|
||||
const debounced = debounce(mockFn, 300);
|
||||
|
||||
debounced('first');
|
||||
vi.advanceTimersByTime(100);
|
||||
|
||||
debounced('second');
|
||||
vi.advanceTimersByTime(100);
|
||||
|
||||
debounced('third');
|
||||
vi.advanceTimersByTime(300);
|
||||
|
||||
expect(mockFn).toHaveBeenCalledTimes(1);
|
||||
expect(mockFn).toHaveBeenCalledWith('third');
|
||||
});
|
||||
|
||||
it('should handle rapid calls correctly', () => {
|
||||
const mockFn = vi.fn();
|
||||
const debounced = debounce(mockFn, 300);
|
||||
|
||||
debounced('1');
|
||||
vi.advanceTimersByTime(50);
|
||||
debounced('2');
|
||||
vi.advanceTimersByTime(50);
|
||||
debounced('3');
|
||||
vi.advanceTimersByTime(50);
|
||||
debounced('4');
|
||||
vi.advanceTimersByTime(300);
|
||||
|
||||
expect(mockFn).toHaveBeenCalledTimes(1);
|
||||
expect(mockFn).toHaveBeenCalledWith('4');
|
||||
});
|
||||
|
||||
it('should not execute if timer is cleared before wait time', () => {
|
||||
const mockFn = vi.fn();
|
||||
const debounced = debounce(mockFn, 300);
|
||||
|
||||
debounced('test');
|
||||
vi.advanceTimersByTime(200);
|
||||
|
||||
expect(mockFn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
43
src/shared/lib/utils/debounce/debounce.ts
Normal file
43
src/shared/lib/utils/debounce/debounce.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* DEBOUNCE UTILITY
|
||||
* ============================================================================
|
||||
*
|
||||
* Creates a debounced function that delays execution until after wait milliseconds
|
||||
* have elapsed since the last time it was invoked.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const debouncedSearch = debounce((query: string) => {
|
||||
* console.log('Searching for:', query);
|
||||
* }, 300);
|
||||
*
|
||||
* debouncedSearch('hello');
|
||||
* debouncedSearch('hello world'); // Only this will execute after 300ms
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a debounced version of a function
|
||||
*
|
||||
* @param fn - The function to debounce
|
||||
* @param wait - The delay in milliseconds
|
||||
* @returns A debounced function that will execute after the specified delay
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
fn: T,
|
||||
wait: number,
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
fn(...args);
|
||||
timeoutId = null;
|
||||
}, wait);
|
||||
};
|
||||
}
|
||||
1
src/shared/lib/utils/debounce/index.ts
Normal file
1
src/shared/lib/utils/debounce/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { debounce } from './debounce';
|
||||
@@ -8,5 +8,6 @@ export {
|
||||
type QueryParamValue,
|
||||
} from './buildQueryString/buildQueryString';
|
||||
export { clampNumber } from './clampNumber/clampNumber';
|
||||
export { debounce } from './debounce/debounce';
|
||||
export { getDecimalPlaces } from './getDecimalPlaces/getDecimalPlaces';
|
||||
export { roundToStepPrecision } from './roundToStepPrecision/roundToStepPrecision';
|
||||
|
||||
Reference in New Issue
Block a user