import { createRef } from 'react'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { MessageBox, MessageBoxHandle } from './MessageBox'; import userEvent from '@testing-library/user-event'; describe('MessageBox', () => { beforeEach(() => { jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { cb(0); // Immediately call the callback return 0; }); }); afterEach(() => { jest.restoreAllMocks(); }); it('should render Message box', () => { render( <>Chatbot Messages ); expect(screen.getByText('Chatbot Messages')).toBeTruthy(); }); it('should assign ref to Message box', () => { const ref = createRef(); render(
Test message content
); screen.getByText('Test message content'); expect(ref.current).not.toBeNull(); // should contain custom methods exposed by the ref expect(typeof ref.current?.isSmartScrollActive).toBe('function'); expect(typeof ref.current?.scrollToTop).toBe('function'); expect(typeof ref.current?.scrollToBottom).toBe('function'); expect(ref.current?.isSmartScrollActive()).toBe(false); expect(ref.current).toBeInstanceOf(HTMLDivElement); }); it('should call onScrollToBottomClick when scroll to bottom button is clicked', async () => { const spy = jest.fn(); render(
Test message content
); // this forces button to show const region = screen.getByRole('region'); Object.defineProperty(region, 'scrollHeight', { configurable: true, value: 1000 }); Object.defineProperty(region, 'clientHeight', { configurable: true, value: 500 }); Object.defineProperty(region, 'scrollTop', { configurable: true, value: 0 }); act(() => { region.dispatchEvent(new Event('scroll')); }); await waitFor(() => { userEvent.click(screen.getByRole('button', { name: /Back to bottom/i })); expect(spy).toHaveBeenCalled(); }); }); it('should call onScrollToTopClick when scroll to top button is clicked', async () => { const spy = jest.fn(); render(
Test message content
); // this forces button to show const region = screen.getByRole('region'); Object.defineProperty(region, 'scrollHeight', { configurable: true, value: 1000 }); Object.defineProperty(region, 'clientHeight', { configurable: true, value: 500 }); Object.defineProperty(region, 'scrollTop', { configurable: true, value: 500 }); act(() => { region.dispatchEvent(new Event('scroll')); }); await waitFor(() => { userEvent.click(screen.getByRole('button', { name: /Back to top/i })); expect(spy).toHaveBeenCalled(); }); }); it('should call user defined onWheel, onTouchMove and onTouchEnd handlers', async () => { const ref = createRef(); const onWheel = jest.fn(); const onTouchMove = jest.fn(); const onTouchEnd = jest.fn(); render(
Test message content
); const element = ref.current!; act(() => { fireEvent.wheel(element, { deltaY: 10 }); fireEvent.touchMove(element, { touches: [{ clientY: 700 }] }); fireEvent.touchEnd(element); }); expect(onWheel).toHaveBeenCalled(); expect(onTouchMove).toHaveBeenCalled(); expect(onTouchEnd).toHaveBeenCalled(); }); it('should scroll to the bottom when the method is called ', async () => { const ref = createRef(); render(
Test message content
); const element = ref.current!; const scrollSpy = jest.spyOn(element, 'scrollTo'); act(() => { ref.current?.scrollToBottom(); ref.current?.scrollToBottom(); ref.current?.scrollToBottom(); }); expect(scrollSpy).toHaveBeenCalledWith({ top: element.scrollHeight, behavior: 'smooth' }); expect(ref.current?.isSmartScrollActive()).toBe(true); }); it('should scroll to the top when the method is called ', async () => { const ref = createRef(); render(
Test message content
); const element = ref.current!; const scrollSpy = jest.spyOn(element, 'scrollTo'); act(() => { ref.current?.scrollToTop(); }); expect(scrollSpy).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' }); expect(ref.current?.isSmartScrollActive()).toBe(false); }); it('should pause automatic scrolling when user scrolls up ', async () => { const ref = createRef(); render(
Test message content
); expect(ref.current?.isSmartScrollActive()).toBe(true); const element = ref.current!; // Manually set scrollHeight and clientHeight for calculations Object.defineProperty(element, 'scrollHeight', { configurable: true, value: 1000 }); Object.defineProperty(element, 'clientHeight', { configurable: true, value: 300 }); // Simulate scroll up by changing scrollTop element.scrollTop = 200; const scrollEvent = new Event('scroll', { bubbles: true }); act(() => { element.dispatchEvent(scrollEvent); }); expect(ref.current?.isSmartScrollActive()).toBe(false); }); it('should resume automatic scrolling when user scrolls down to the bottom using scroll event', async () => { jest.useFakeTimers(); const ref = createRef(); render(
Test message content
); const element = ref.current!; // Manually set scrollHeight and clientHeight for calculations Object.defineProperty(element, 'scrollHeight', { configurable: true, value: 1000 }); Object.defineProperty(element, 'clientHeight', { configurable: true, value: 300 }); // Simulate scroll up by changing scrollTop element.scrollTop = 100; const scrollEvent = new Event('scroll', { bubbles: true }); act(() => { element.dispatchEvent(scrollEvent); }); expect(ref.current?.isSmartScrollActive()).toBe(false); act(() => { // Simulate scroll down by changing scrollTop element.scrollTop = 650; // scrollHeight - scrollTop - clientHeight - DELTA_DOWN (50) = 0 const scrollEvent = new Event('scroll', { bubbles: true }); element.dispatchEvent(scrollEvent); jest.advanceTimersByTime(250); }); expect(ref.current?.isSmartScrollActive()).toBe(true); jest.useRealTimers(); }); it('should resume automatic scrolling when scrollToBottom method is used', async () => { const ref = createRef(); render(
Test message content
); const element = ref.current!; // Manually set scrollHeight and clientHeight for calculations Object.defineProperty(element, 'scrollHeight', { configurable: true, value: 1000 }); Object.defineProperty(element, 'clientHeight', { configurable: true, value: 300 }); // Simulate scroll up by changing scrollTop element.scrollTop = 100; const scrollEvent = new Event('scroll', { bubbles: true }); act(() => { element.dispatchEvent(scrollEvent); }); expect(ref.current?.isSmartScrollActive()).toBe(false); act(() => { ref.current?.scrollToBottom({ resumeSmartScroll: true, behavior: 'auto' }); // resumes auto scroll and scrolls to bottom. }); expect(ref.current?.isSmartScrollActive()).toBe(true); }); it('should resume automatic scrolling when mouse wheel event is used', async () => { const ref = createRef(); render(
Test message content
); const element = ref.current!; // Manually set scrollHeight and clientHeight for calculations Object.defineProperty(element, 'scrollHeight', { configurable: true, value: 1000 }); Object.defineProperty(element, 'clientHeight', { configurable: true, value: 300 }); Object.defineProperty(element, 'scrollTop', { configurable: true, value: 350 }); const scrollEvent = new Event('scroll', { bubbles: true }); act(() => { element.dispatchEvent(scrollEvent); }); expect(ref.current?.isSmartScrollActive()).toBe(false); // Simulate mouse wheel event act(() => { Object.defineProperty(element, 'scrollTop', { configurable: true, value: 650 }); fireEvent.wheel(element, { deltaY: 10 }); }); expect(ref.current?.isSmartScrollActive()).toBe(true); }); it('should resume automatic scrolling when user swipes up in touch screen', async () => { const ref = createRef(); render(
Test message content
); const element = ref.current!; // Manually set scrollHeight and clientHeight for calculations Object.defineProperty(element, 'scrollHeight', { configurable: true, value: 1000 }); Object.defineProperty(element, 'clientHeight', { configurable: true, value: 300 }); Object.defineProperty(element, 'scrollTop', { configurable: true, value: 350 }); const scrollEvent = new Event('scroll', { bubbles: true }); act(() => { element.dispatchEvent(scrollEvent); }); expect(ref.current?.isSmartScrollActive()).toBe(false); // Simulate touch event - swipe up act(() => { Object.defineProperty(element, 'scrollTop', { configurable: true, value: 650 }); fireEvent.touchStart(element, { touches: [{ clientY: 700 }] }); fireEvent.touchMove(element, { touches: [{ clientY: 700 }] }); fireEvent.touchMove(element, { touches: [{ clientY: 600 }] }); fireEvent.touchEnd(element); }); expect(ref.current?.isSmartScrollActive()).toBe(true); }); });