forked from cardosofelipe/fast-next-template
- Add agent types list page with search and filter functionality - Add agent type detail/edit page with tabbed interface - Create AgentTypeForm component with React Hook Form + Zod validation - Implement model configuration (temperature, max tokens, top_p) - Add MCP permission management with checkboxes - Include personality prompt editor textarea - Create TanStack Query hooks for agent-types API - Add useDebounce hook for search optimization - Comprehensive unit tests for all components (68 tests) Components: - AgentTypeList: Grid view with status badges, expertise tags - AgentTypeDetail: Full detail view with model config, MCP permissions - AgentTypeForm: Create/edit with 4 tabs (Basic, Model, Permissions, Personality) Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
155 lines
4.0 KiB
TypeScript
155 lines
4.0 KiB
TypeScript
import { renderHook, act } from '@testing-library/react';
|
|
import { useDebounce } from '@/lib/hooks/useDebounce';
|
|
|
|
describe('useDebounce', () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it('returns the initial value immediately', () => {
|
|
const { result } = renderHook(() => useDebounce('initial', 500));
|
|
expect(result.current).toBe('initial');
|
|
});
|
|
|
|
it('updates the debounced value after the delay', () => {
|
|
const { result, rerender } = renderHook(
|
|
({ value, delay }) => useDebounce(value, delay),
|
|
{ initialProps: { value: 'initial', delay: 500 } }
|
|
);
|
|
|
|
// Change the value
|
|
rerender({ value: 'updated', delay: 500 });
|
|
|
|
// Value should still be initial before delay
|
|
expect(result.current).toBe('initial');
|
|
|
|
// Fast forward time
|
|
act(() => {
|
|
jest.advanceTimersByTime(500);
|
|
});
|
|
|
|
// Value should now be updated
|
|
expect(result.current).toBe('updated');
|
|
});
|
|
|
|
it('does not update the value before the delay', () => {
|
|
const { result, rerender } = renderHook(
|
|
({ value, delay }) => useDebounce(value, delay),
|
|
{ initialProps: { value: 'initial', delay: 500 } }
|
|
);
|
|
|
|
rerender({ value: 'updated', delay: 500 });
|
|
|
|
// Only advance 300ms (not enough)
|
|
act(() => {
|
|
jest.advanceTimersByTime(300);
|
|
});
|
|
|
|
expect(result.current).toBe('initial');
|
|
});
|
|
|
|
it('resets the timer when value changes rapidly', () => {
|
|
const { result, rerender } = renderHook(
|
|
({ value, delay }) => useDebounce(value, delay),
|
|
{ initialProps: { value: 'initial', delay: 500 } }
|
|
);
|
|
|
|
// First change
|
|
rerender({ value: 'first', delay: 500 });
|
|
|
|
// Advance 300ms
|
|
act(() => {
|
|
jest.advanceTimersByTime(300);
|
|
});
|
|
|
|
// Second change (should reset timer)
|
|
rerender({ value: 'second', delay: 500 });
|
|
|
|
// Advance another 300ms (total 600ms from first, but only 300ms from second)
|
|
act(() => {
|
|
jest.advanceTimersByTime(300);
|
|
});
|
|
|
|
// Value should still be initial (timer was reset)
|
|
expect(result.current).toBe('initial');
|
|
|
|
// Advance the remaining 200ms
|
|
act(() => {
|
|
jest.advanceTimersByTime(200);
|
|
});
|
|
|
|
// Now should show 'second'
|
|
expect(result.current).toBe('second');
|
|
});
|
|
|
|
it('cleans up timeout on unmount', () => {
|
|
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
|
|
|
|
const { unmount, rerender } = renderHook(
|
|
({ value, delay }) => useDebounce(value, delay),
|
|
{ initialProps: { value: 'initial', delay: 500 } }
|
|
);
|
|
|
|
rerender({ value: 'updated', delay: 500 });
|
|
unmount();
|
|
|
|
expect(clearTimeoutSpy).toHaveBeenCalled();
|
|
clearTimeoutSpy.mockRestore();
|
|
});
|
|
|
|
it('works with different delay values', () => {
|
|
const { result, rerender } = renderHook(
|
|
({ value, delay }) => useDebounce(value, delay),
|
|
{ initialProps: { value: 'initial', delay: 1000 } }
|
|
);
|
|
|
|
rerender({ value: 'updated', delay: 1000 });
|
|
|
|
act(() => {
|
|
jest.advanceTimersByTime(500);
|
|
});
|
|
|
|
expect(result.current).toBe('initial');
|
|
|
|
act(() => {
|
|
jest.advanceTimersByTime(500);
|
|
});
|
|
|
|
expect(result.current).toBe('updated');
|
|
});
|
|
|
|
it('works with different value types', () => {
|
|
// Test with number
|
|
const { result: numberResult } = renderHook(() => useDebounce(42, 500));
|
|
expect(numberResult.current).toBe(42);
|
|
|
|
// Test with object
|
|
const obj = { foo: 'bar' };
|
|
const { result: objectResult } = renderHook(() => useDebounce(obj, 500));
|
|
expect(objectResult.current).toEqual({ foo: 'bar' });
|
|
|
|
// Test with null
|
|
const { result: nullResult } = renderHook(() => useDebounce(null, 500));
|
|
expect(nullResult.current).toBeNull();
|
|
});
|
|
|
|
it('handles zero delay', () => {
|
|
const { result, rerender } = renderHook(
|
|
({ value, delay }) => useDebounce(value, delay),
|
|
{ initialProps: { value: 'initial', delay: 0 } }
|
|
);
|
|
|
|
rerender({ value: 'updated', delay: 0 });
|
|
|
|
act(() => {
|
|
jest.advanceTimersByTime(0);
|
|
});
|
|
|
|
expect(result.current).toBe('updated');
|
|
});
|
|
});
|