forked from cardosofelipe/fast-next-template
- Consolidated and modularized `eslint.config.mjs` with defined rules for source, test, E2E, and scripts. - Improved test and E2E rules with relaxed settings for flexibility and enhanced mocking. - Standardized variable naming and removed redundant imports in unit and E2E tests. - Updated error handling and comments to align with modern TypeScript best practices (e.g., `@ts-expect-error`).
179 lines
5.0 KiB
TypeScript
179 lines
5.0 KiB
TypeScript
/**
|
|
* Tests for usePrefersReducedMotion hook
|
|
* Tests media query detection for accessibility preferences
|
|
*/
|
|
|
|
import { renderHook, act } from '@testing-library/react';
|
|
import { usePrefersReducedMotion } from '@/hooks/usePrefersReducedMotion';
|
|
|
|
describe('usePrefersReducedMotion', () => {
|
|
let mockMatchMedia: jest.Mock;
|
|
let mockListeners: ((event: MediaQueryListEvent) => void)[];
|
|
|
|
beforeEach(() => {
|
|
mockListeners = [];
|
|
|
|
mockMatchMedia = jest.fn().mockImplementation((query: string) => ({
|
|
matches: false,
|
|
media: query,
|
|
onchange: null,
|
|
addEventListener: jest.fn((event: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
if (event === 'change') {
|
|
mockListeners.push(listener);
|
|
}
|
|
}),
|
|
removeEventListener: jest.fn((event: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
if (event === 'change') {
|
|
const index = mockListeners.indexOf(listener);
|
|
if (index > -1) {
|
|
mockListeners.splice(index, 1);
|
|
}
|
|
}
|
|
}),
|
|
dispatchEvent: jest.fn(),
|
|
}));
|
|
|
|
Object.defineProperty(window, 'matchMedia', {
|
|
writable: true,
|
|
value: mockMatchMedia,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('returns false when user does not prefer reduced motion', () => {
|
|
mockMatchMedia.mockImplementation((query: string) => ({
|
|
matches: false,
|
|
media: query,
|
|
onchange: null,
|
|
addEventListener: jest.fn(),
|
|
removeEventListener: jest.fn(),
|
|
dispatchEvent: jest.fn(),
|
|
}));
|
|
|
|
const { result } = renderHook(() => usePrefersReducedMotion());
|
|
|
|
expect(result.current).toBe(false);
|
|
expect(mockMatchMedia).toHaveBeenCalledWith('(prefers-reduced-motion: reduce)');
|
|
});
|
|
|
|
it('returns true when user prefers reduced motion', () => {
|
|
mockMatchMedia.mockImplementation((query: string) => ({
|
|
matches: true,
|
|
media: query,
|
|
onchange: null,
|
|
addEventListener: jest.fn(),
|
|
removeEventListener: jest.fn(),
|
|
dispatchEvent: jest.fn(),
|
|
}));
|
|
|
|
const { result } = renderHook(() => usePrefersReducedMotion());
|
|
|
|
expect(result.current).toBe(true);
|
|
});
|
|
|
|
it('updates when media query preference changes to true', () => {
|
|
const mockMediaQuery = {
|
|
matches: false,
|
|
media: '(prefers-reduced-motion: reduce)',
|
|
onchange: null,
|
|
addEventListener: jest.fn((event: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
if (event === 'change') {
|
|
mockListeners.push(listener);
|
|
}
|
|
}),
|
|
removeEventListener: jest.fn(),
|
|
dispatchEvent: jest.fn(),
|
|
};
|
|
|
|
mockMatchMedia.mockReturnValue(mockMediaQuery);
|
|
|
|
const { result } = renderHook(() => usePrefersReducedMotion());
|
|
|
|
expect(result.current).toBe(false);
|
|
|
|
// Simulate media query change
|
|
act(() => {
|
|
mockListeners.forEach(listener => {
|
|
listener({ matches: true } as MediaQueryListEvent);
|
|
});
|
|
});
|
|
|
|
expect(result.current).toBe(true);
|
|
});
|
|
|
|
it('updates when media query preference changes to false', () => {
|
|
const mockMediaQuery = {
|
|
matches: true,
|
|
media: '(prefers-reduced-motion: reduce)',
|
|
onchange: null,
|
|
addEventListener: jest.fn((event: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
if (event === 'change') {
|
|
mockListeners.push(listener);
|
|
}
|
|
}),
|
|
removeEventListener: jest.fn(),
|
|
dispatchEvent: jest.fn(),
|
|
};
|
|
|
|
mockMatchMedia.mockReturnValue(mockMediaQuery);
|
|
|
|
const { result } = renderHook(() => usePrefersReducedMotion());
|
|
|
|
expect(result.current).toBe(true);
|
|
|
|
// Simulate media query change
|
|
act(() => {
|
|
mockListeners.forEach(listener => {
|
|
listener({ matches: false } as MediaQueryListEvent);
|
|
});
|
|
});
|
|
|
|
expect(result.current).toBe(false);
|
|
});
|
|
|
|
it('cleans up event listener on unmount', () => {
|
|
const removeEventListenerSpy = jest.fn();
|
|
|
|
const mockMediaQuery = {
|
|
matches: false,
|
|
media: '(prefers-reduced-motion: reduce)',
|
|
onchange: null,
|
|
addEventListener: jest.fn((event: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
if (event === 'change') {
|
|
mockListeners.push(listener);
|
|
}
|
|
}),
|
|
removeEventListener: removeEventListenerSpy,
|
|
dispatchEvent: jest.fn(),
|
|
};
|
|
|
|
mockMatchMedia.mockReturnValue(mockMediaQuery);
|
|
|
|
const { unmount } = renderHook(() => usePrefersReducedMotion());
|
|
|
|
expect(mockMediaQuery.addEventListener).toHaveBeenCalledWith('change', expect.any(Function));
|
|
|
|
unmount();
|
|
|
|
expect(removeEventListenerSpy).toHaveBeenCalledWith('change', expect.any(Function));
|
|
});
|
|
|
|
it('handles SSR environment safely', () => {
|
|
const originalWindow = global.window;
|
|
|
|
// @ts-expect-error - Simulating SSR
|
|
delete global.window;
|
|
|
|
const { result } = renderHook(() => usePrefersReducedMotion());
|
|
|
|
// Should return false in SSR environment
|
|
expect(result.current).toBe(false);
|
|
|
|
// Restore window
|
|
global.window = originalWindow;
|
|
});
|
|
});
|