Skip to content
Snippets Groups Projects
Unverified Commit 62660fc4 authored by Yash Rajpal's avatar Yash Rajpal Committed by GitHub
Browse files

fix: Restore room scroll position (#34908)

parent e6eff85d
No related branches found
No related tags found
No related merge requests found
---
'@rocket.chat/meteor': patch
---
Fixes an issue where room scroll position wasn't being restored when moving between rooms.
import { renderHook } from '@testing-library/react';
import React from 'react';
import { useRestoreScrollPosition } from './useRestoreScrollPosition';
import { RoomManager } from '../../../../lib/RoomManager';
jest.mock('../../../../lib/RoomManager', () => ({
RoomManager: {
getStore: jest.fn(),
},
}));
describe('useRestoreScrollPosition', () => {
it('should restore room scroll position based on store', () => {
(RoomManager.getStore as jest.Mock).mockReturnValue({ scroll: 100, atBottom: false });
const mockElement = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: mockElement });
const { unmount } = renderHook(() => useRestoreScrollPosition('room-id'), { legacyRoot: true });
expect(useRefSpy).toHaveBeenCalledWith(null);
expect(mockElement).toHaveProperty('scrollTop', 100);
expect(mockElement).toHaveProperty('scrollLeft', 30);
unmount();
expect(mockElement.removeEventListener).toHaveBeenCalledWith('scroll', expect.any(Function));
});
it('should not restore scroll position if already at bottom', () => {
(RoomManager.getStore as jest.Mock).mockReturnValue({ scroll: 100, atBottom: true });
const mockElement = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
scrollHeight: 800,
};
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: mockElement });
const { unmount } = renderHook(() => useRestoreScrollPosition('room-id'), { legacyRoot: true });
expect(useRefSpy).toHaveBeenCalledWith(null);
expect(mockElement).toHaveProperty('scrollTop', 800);
expect(mockElement).not.toHaveProperty('scrollLeft');
unmount();
expect(mockElement.removeEventListener).toHaveBeenCalledWith('scroll', expect.any(Function));
});
it('should update store based on scroll position', () => {
const update = jest.fn();
(RoomManager.getStore as jest.Mock).mockReturnValue({ update });
const mockElement = {
addEventListener: jest.fn((event, handler) => {
if (event === 'scroll') {
handler({
target: {
scrollTop: 500,
},
});
}
}),
removeEventListener: jest.fn(),
};
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: mockElement });
const { unmount } = renderHook(() => useRestoreScrollPosition('room-id'), { legacyRoot: true });
expect(useRefSpy).toHaveBeenCalledWith(null);
expect(update).toHaveBeenCalledWith({ scroll: 500, atBottom: false });
unmount();
expect(mockElement.removeEventListener).toHaveBeenCalledWith('scroll', expect.any(Function));
});
});
import type { IRoom } from '@rocket.chat/core-typings'; import type { IRoom } from '@rocket.chat/core-typings';
import { useMergedRefs } from '@rocket.chat/fuselage-hooks'; import { useCallback, useEffect, useRef } from 'react';
import type { RefObject } from 'react';
import { useCallback } from 'react';
import { isAtBottom } from '../../../../../app/ui/client/views/app/lib/scrolling'; import { isAtBottom } from '../../../../../app/ui/client/views/app/lib/scrolling';
import { withThrottling } from '../../../../../lib/utils/highOrderFunctions'; import { withThrottling } from '../../../../../lib/utils/highOrderFunctions';
import { RoomManager } from '../../../../lib/RoomManager'; import { RoomManager } from '../../../../lib/RoomManager';
export function useRestoreScrollPosition(roomId: IRoom['_id']) { export function useRestoreScrollPosition(roomId: IRoom['_id']) {
const ref = useCallback( const ref = useRef<HTMLElement>(null);
(node: HTMLElement | null) => {
if (!node) { const handleRestoreScroll = useCallback(() => {
return; if (!ref.current) {
} return;
const store = RoomManager.getStore(roomId); }
if (store?.scroll && !store.atBottom) { const store = RoomManager.getStore(roomId);
node.scrollTo({
left: 30, if (store?.scroll && !store.atBottom) {
top: store.scroll, ref.current.scrollTop = store.scroll;
}); ref.current.scrollLeft = 30;
} else { } else {
node.scrollTo({ ref.current.scrollTop = ref.current.scrollHeight;
top: node.scrollHeight, }
}); }, [roomId]);
}
}, useEffect(() => {
[roomId], if (!ref.current) {
); return;
}
const refCallback = useCallback(
(node: HTMLElement | null) => { handleRestoreScroll();
if (!node) {
return; const refValue = ref.current;
} const store = RoomManager.getStore(roomId);
const store = RoomManager.getStore(roomId); const handleWrapperScroll = withThrottling({ wait: 100 })((event) => {
store?.update({ scroll: event.target.scrollTop, atBottom: isAtBottom(event.target, 50) });
const handleWrapperScroll = withThrottling({ wait: 100 })(() => { });
store?.update({ scroll: node.scrollTop, atBottom: isAtBottom(node, 50) });
}); refValue.addEventListener('scroll', handleWrapperScroll, { passive: true });
node.addEventListener('scroll', handleWrapperScroll, { return () => {
passive: true, refValue.removeEventListener('scroll', handleWrapperScroll);
}); };
}, }, [roomId, handleRestoreScroll]);
[roomId],
);
return { return {
innerRef: useMergedRefs(refCallback, ref) as unknown as RefObject<HTMLElement>, innerRef: ref,
}; };
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment