Files
frontend-app-authoring/src/hooks.test.ts
2025-04-02 12:04:48 -04:00

121 lines
3.6 KiB
TypeScript

import { fireEvent, renderHook } from '@testing-library/react';
import { useLocation } from 'react-router-dom';
import { history } from '@edx/frontend-platform';
import { useScrollToHashElement, useEscapeClick, useLoadOnScroll } from './hooks';
jest.mock('react-router-dom', () => ({
useLocation: jest.fn(),
}));
jest.mock('@edx/frontend-platform', () => ({
history: {
replace: jest.fn(),
},
}));
describe('Custom Hooks', () => {
describe('useScrollToHashElement', () => {
beforeEach(() => {
window.location.hash = '#test';
document.body.innerHTML = '<div id="test">Test Element</div>';
Element.prototype.scrollIntoView = jest.fn();
jest.mocked(useLocation).mockReturnValue({
pathname: '/test',
state: null,
search: '',
hash: '',
key: 'default',
});
});
afterEach(() => {
jest.clearAllMocks();
window.location.hash = '';
});
it('scrolls to element with the hash and replaces the URL hash', () => {
renderHook(() => useScrollToHashElement({ isLoading: false }));
const element = document.getElementById('test');
expect(element).toBeInTheDocument();
expect(element?.scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' });
expect(history.replace).toHaveBeenCalledWith({ pathname: '/test', hash: '' });
});
});
describe('useEscapeClick', () => {
it('calls onEscape when Escape key is pressed', () => {
const onEscape = jest.fn();
renderHook(() => useEscapeClick({ onEscape, dependency: [] }));
fireEvent.keyDown(window, { key: 'Escape' });
expect(onEscape).toHaveBeenCalledTimes(1);
});
it('does not call onEscape for other keys', () => {
const onEscape = jest.fn();
renderHook(() => useEscapeClick({ onEscape, dependency: [] }));
fireEvent.keyDown(window, { key: 'Enter' });
expect(onEscape).not.toHaveBeenCalled();
});
});
describe('useLoadOnScroll', () => {
const fetchNextPage = jest.fn();
afterEach(() => {
jest.clearAllMocks();
});
it('calls fetchNextPage when scrolled near the bottom', () => {
renderHook(() => useLoadOnScroll(true, false, fetchNextPage, true));
Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 1000 });
Object.defineProperty(document.body, 'scrollHeight', { writable: true, configurable: true, value: 1500 });
window.scrollY = 1200;
fireEvent.scroll(window);
// Called on scroll once and then due to content being less than screen height
// and hasNextPage being true.
expect(fetchNextPage).toHaveBeenCalledTimes(2);
});
it('does not call fetchNextPage if not near the bottom', () => {
renderHook(() => useLoadOnScroll(true, false, fetchNextPage, true));
Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 1000 });
Object.defineProperty(document.body, 'scrollHeight', { writable: true, configurable: true, value: 2000 });
window.scrollY = 500;
fireEvent.scroll(window);
expect(fetchNextPage).not.toHaveBeenCalled();
});
it('does not call fetchNextPage if fetching is in progress', () => {
renderHook(() => useLoadOnScroll(true, true, fetchNextPage, true));
fireEvent.scroll(window);
expect(fetchNextPage).not.toHaveBeenCalled();
});
it('does not call fetchNextPage if hasNextPage is false', () => {
renderHook(() => useLoadOnScroll(false, false, fetchNextPage, true));
fireEvent.scroll(window);
expect(fetchNextPage).not.toHaveBeenCalled();
});
});
});